├── documentation └── deployment │ ├── create_automated_build.png │ ├── create_github_autobuild.png │ ├── Deployment.DockerHubCloudBuild.md │ └── Deployment.LocalDockerBuild.md ├── generators ├── common │ ├── testdata │ │ └── main.go │ ├── import_bag_test.go │ └── import_bag.go └── eventgrid │ ├── type_stub_test.go │ ├── testdata │ └── main.go │ ├── template_cache.go │ ├── type_stub.go │ ├── templates │ └── actions │ │ └── eventgrid_name.go.tmpl │ ├── builder │ ├── builder_test.go │ └── builder.go │ ├── generator_test.go │ ├── generator.go │ └── static_templates.go ├── .gitignore ├── sdk ├── eventgrid │ ├── testdata │ │ └── sample_subscriptionvalidationevent.json │ ├── app.go │ ├── simple_subscriber.go │ ├── router.go │ ├── type_dispatch_subscriber_test.go │ ├── cache_test.go │ ├── subscription_validation.go │ ├── context_test.go │ ├── subscriber.go │ ├── cache.go │ ├── type_dispatch_subscriber.go │ ├── subscription_validation_test.go │ └── context.go ├── go.mod └── go.sum ├── cmd ├── eventgrid_types_test.go ├── testdata │ ├── parameters1.json │ └── template1.json ├── deployment.go ├── eventgrid_test.go ├── version.go ├── azure.go ├── available.go ├── deployment_test.go ├── eventgrid_types.go ├── root.go ├── eventgrid.go ├── provision_test.go └── provision.go ├── .travis.yml ├── go.mod ├── LICENSE ├── main.go ├── SECURITY.md ├── README.md └── go.sum /documentation/deployment/create_automated_build.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/buffalo-azure/HEAD/documentation/deployment/create_automated_build.png -------------------------------------------------------------------------------- /documentation/deployment/create_github_autobuild.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Azure/buffalo-azure/HEAD/documentation/deployment/create_github_autobuild.png -------------------------------------------------------------------------------- /generators/common/testdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | _ "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid" 7 | ) 8 | 9 | func main() { 10 | fmt.Println("Hello, world!!!") 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.dll 4 | *.so 5 | *.dylib 6 | 7 | # Test binary, build with `go test -c` 8 | *.test 9 | 10 | # Output of the go coverage tool, specifically when used with LiteIDE 11 | *.out 12 | 13 | # Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736 14 | .glide/ 15 | 16 | vendor/ 17 | 18 | .idea/ 19 | 20 | -------------------------------------------------------------------------------- /generators/eventgrid/type_stub_test.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func TestTypeStub_IsType(t *testing.T) { 9 | subject := &TypeStub{} 10 | m := reflect.TypeOf((*reflect.Type)(nil)).Elem() 11 | 12 | if !reflect.TypeOf(subject).Implements(m) { 13 | t.Log("TypeStub does not implement reflect.Type") 14 | t.Fail() 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /sdk/eventgrid/testdata/sample_subscriptionvalidationevent.json: -------------------------------------------------------------------------------- 1 | [{"id": "2d1781af-3a4c-4d7c-bd0c-e34b19da4e66","topic": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx","subject": "","data": {"validationCode": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6"},"eventType": "Microsoft.EventGrid.SubscriptionValidationEvent","eventTime": "2018-01-25T22:12:19.4556811Z","metadataVersion": "1","dataVersion": "1"}] -------------------------------------------------------------------------------- /generators/eventgrid/testdata/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | egdp "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid" 7 | "github.com/Azure/buffalo-azure/sdk/eventgrid" 8 | ) 9 | 10 | func main() { 11 | fmt.Printf("%#v\n", eventgrid.TypeDispatchSubscriber{}) 12 | fmt.Printf("%#v\n", egdp.StorageBlobCreatedEventData{}) 13 | } 14 | -------------------------------------------------------------------------------- /cmd/eventgrid_types_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | ) 7 | 8 | func Test_wellKnownEvents_trimmed(t *testing.T) { 9 | assertTrimmed := func(t *testing.T, subject string) { 10 | if subject != strings.TrimSpace(subject) { 11 | t.Logf("%q has leading or trailing whitespace", subject) 12 | t.Fail() 13 | } 14 | } 15 | 16 | for k, v := range wellKnownEvents { 17 | assertTrimmed(t, k) 18 | assertTrimmed(t, v) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.11.x 5 | - master 6 | 7 | matrix: 8 | fast_finish: true 9 | allow_failures: 10 | - go: master 11 | 12 | install: 13 | - go get -u golang.org/x/lint/golint 14 | 15 | script: 16 | - go generate ./... 17 | - test -z "$(git diff ./generators/eventgrid/static_templates.go | tee /dev/stderr)" 18 | - go build all 19 | - go test -race ./... 20 | - test -z "$(go fmt ./... | tee /dev/stderr)" 21 | - test -z "$(golint $(go list ./...) | tee /dev/stderr)" 22 | - go vet ./... 23 | 24 | env: 25 | - GO111MODULE=on 26 | -------------------------------------------------------------------------------- /sdk/eventgrid/app.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "github.com/gobuffalo/buffalo" 5 | ) 6 | 7 | // RegisterSubscriber updates a `buffalo.App` to route requests to a particular 8 | // subscriber. 9 | // This method is the spiritual equivalent of `App.Resource`: 10 | // https://godoc.org/github.com/gobuffalo/buffalo#App.Resource 11 | func RegisterSubscriber(app *buffalo.App, route string, s Subscriber) *buffalo.App { 12 | group := app.Group(route) 13 | 14 | route = "/" 15 | 16 | group.POST(route, SubscriptionValidationMiddleware(s.Receive)) 17 | group.GET(route, s.List) 18 | group.GET(route+"{event_id}", s.Show) 19 | 20 | return group 21 | } 22 | -------------------------------------------------------------------------------- /cmd/testdata/parameters1.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "database": { 6 | "value": "postgresql" 7 | }, 8 | "databaseAdministratorLogin": { 9 | "value": "buffaloAdmin" 10 | }, 11 | "databaseName": { 12 | "value": "buffalo30_development" 13 | }, 14 | "imageName": { 15 | "value": "marstr/quickstart:latest" 16 | }, 17 | "name": { 18 | "value": "buffalo-app-uqqq1nfno1" 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /sdk/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Azure/buffalo-azure/sdk 2 | 3 | require ( 4 | github.com/cockroachdb/apd v1.1.0 // indirect 5 | github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c // indirect 6 | github.com/gobuffalo/buffalo v0.13.0 7 | github.com/gobuffalo/fizz v1.0.12 // indirect 8 | github.com/gobuffalo/uuid v2.0.4+incompatible 9 | github.com/gofrs/uuid v3.1.0+incompatible // indirect 10 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 // indirect 11 | github.com/jackc/pgx v3.2.0+incompatible // indirect 12 | github.com/jmoiron/sqlx v1.2.0 // indirect 13 | github.com/pkg/errors v0.8.0 14 | github.com/satori/go.uuid v1.2.0 // indirect 15 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 // indirect 16 | ) 17 | -------------------------------------------------------------------------------- /sdk/eventgrid/simple_subscriber.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "github.com/gobuffalo/buffalo" 5 | ) 6 | 7 | // SimpleSubscriber only fulfills the "Receive" portion of the Subscriber interface. 8 | // It is equivalent to creating a `TypeDispatchSubscriber` but only binding an `EventHandler` 9 | // to the `EventTypeWildcard` type. 10 | type SimpleSubscriber struct { 11 | Subscriber 12 | EventHandler 13 | } 14 | 15 | // Receive unmarshals the body of the request as an Event Grid Event, and hands it to the 16 | // EventHandler for further processing. 17 | func (s SimpleSubscriber) Receive(c buffalo.Context) (err error) { 18 | var event Event 19 | 20 | err = c.Bind(&event) 21 | if err != nil { 22 | return 23 | } 24 | 25 | return s.EventHandler(c, event) 26 | } 27 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/Azure/buffalo-azure 2 | 3 | require ( 4 | github.com/Azure/azure-sdk-for-go v18.0.0+incompatible 5 | github.com/Azure/buffalo-azure/sdk v0.1.0 6 | github.com/Azure/go-autorest v10.12.0+incompatible 7 | github.com/gobuffalo/buffalo v0.13.0 8 | github.com/gobuffalo/buffalo-plugins v1.0.4 9 | github.com/gobuffalo/makr v1.1.5 10 | github.com/gobuffalo/pop v4.8.4+incompatible 11 | github.com/joho/godotenv v1.3.0 12 | github.com/markbates/inflect v1.0.1 13 | github.com/marstr/collection v0.3.3 // indirect 14 | github.com/marstr/randname v0.0.0-20180611202505-48a63b6052f1 15 | github.com/mitchellh/go-homedir v1.0.0 16 | github.com/sirupsen/logrus v1.1.1 17 | github.com/spf13/cobra v0.0.3 18 | github.com/spf13/viper v1.2.1 19 | ) 20 | 21 | replace github.com/Azure/buffalo-azure/sdk => ./sdk 22 | -------------------------------------------------------------------------------- /cmd/deployment.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | // DeploymentParameters enables easy marshaling of ARM Template deployment parameters. 4 | type DeploymentParameters struct { 5 | Schema string `json:"$schema"` 6 | ContentVersion string `json:"contentVersion"` 7 | Parameters map[string]DeploymentParameter `json:"parameters"` 8 | } 9 | 10 | // DeploymentParameter is an individual entry in the parameter list. 11 | type DeploymentParameter struct { 12 | Value interface{} `json:"value,omitempty"` 13 | } 14 | 15 | // NewDeploymentParameters creates a new instance of DeploymentParameters with reasonable defaults but no parameters. 16 | func NewDeploymentParameters() *DeploymentParameters { 17 | return &DeploymentParameters{ 18 | Schema: "http://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", 19 | ContentVersion: "1.0.0.0", 20 | Parameters: make(map[string]DeploymentParameter), 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /generators/eventgrid/template_cache.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | ) 8 | 9 | // TemplateCache stores files used as templates, so that they will always be distributed along 10 | // with thie buffalo-azure binary. 11 | type TemplateCache map[string][]byte 12 | 13 | // Rehydrate writes the contents of each template file back to disk, rooted at the directory 14 | // specified. 15 | func (c TemplateCache) Rehydrate(root string) error { 16 | for filename, contents := range c { 17 | desiredPath := path.Join(root, filename) 18 | if err := os.MkdirAll(path.Dir(desiredPath), os.ModePerm); err != nil { 19 | return err 20 | } 21 | if err := ioutil.WriteFile(desiredPath, contents, os.ModePerm); err != nil { 22 | return err 23 | } 24 | } 25 | return nil 26 | } 27 | 28 | // Clear removes all entries from a TemplateCache, so that they can be collected by the 29 | // garbage collector. 30 | func (c TemplateCache) Clear() { 31 | for k := range c { 32 | delete(c, k) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /sdk/eventgrid/router.go: -------------------------------------------------------------------------------- 1 | // Package eventgrid aims to provide a shim for easily working with Event Grid 2 | // from a gobuffalo application. It will frequently be utilized by the code 3 | // generated for extending your app to receive Event Grid Events. 4 | package eventgrid 5 | 6 | import ( 7 | "path" 8 | 9 | "github.com/gobuffalo/buffalo" 10 | ) 11 | 12 | // EventTypeWildcard is a special-case value that can be used when subscribing 13 | // to an EventGrid topic. 14 | const EventTypeWildcard = "all" 15 | 16 | // App extends the functionality of a normal buffalo.App with actions 17 | // specific to Event Grid. Specifically, it seeks to allow quick and easy 18 | // register a Group of actions for processing and reasoning. 19 | type App buffalo.App 20 | 21 | // Subscriber creates a group of mappings (*buffalo.App) between 22 | // a Subscriber interface implementation and the appropriate REST 23 | // paths. 24 | func (a *App) Subscriber(p string, s Subscriber) *buffalo.App { 25 | g := (*buffalo.App)(a).Group(p) 26 | p = "/" 27 | 28 | g.POST(p, s.Receive) 29 | if a.Env == "development" { 30 | g.GET(path.Join(p, "new"), s.New) 31 | } 32 | 33 | return g 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation and contributors. All rights reserved. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package main 22 | 23 | import "github.com/Azure/buffalo-azure/cmd" 24 | 25 | func main() { 26 | cmd.Execute() 27 | } 28 | -------------------------------------------------------------------------------- /documentation/deployment/Deployment.DockerHubCloudBuild.md: -------------------------------------------------------------------------------- 1 | ## Continuous Deployment using GitHub and Docker Hub Repositories 2 | 3 | #### Pre-Requisites 4 | 5 | - Your Buffalo application's source code must be available in a public GitHub repository. 6 | - You must have "Admin" access to the GitHub repository. 7 | - You must have a Dockerfile in the root of your repository. 8 | - You must have Docker Hub account. 9 | 10 | ### Step-By-Step 11 | 12 | 1. Ensure your code is up-to-date on GitHub, using `git push`. 13 | 1. Navigate a web browser to https://hub.docker.com. 14 | 1. Create an Automated Build by clicking through the "Create" menu at the top of the screen: 15 | ![Create Build](./create_automated_build.png) 16 | 1. Ensure your GitHub account is linked to your Docker Hub account. 17 | 1. Select "Create Auto-build Github" (This step can be slow as it loads all repos you have access to, be patient): 18 | ![GitHub](./create_github_autobuild.png) 19 | 1. Select the repository that contains your project. 20 | 1. Navigate to your "App Service" in the Azure portal. 21 | 1. Click on the "Container Settings" option. 22 | 1. In the "Continuous Deployment" section, switch it "On". 23 | 1. Click on "Show Url", and copy the "WEBHOOK URL" 24 | 1. Navigate back to the Automated Build in Docker Hub. 25 | 1. Click on the "Webhooks" tab 26 | 1. Add a Webhook by clicking on the "+" sign. 27 | 1. Give the webhook an appropriate name, like "Publish-App" 28 | 1. Paste the URL from your App Service. 29 | 1. Click "Save" -------------------------------------------------------------------------------- /generators/eventgrid/type_stub.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "errors" 5 | "reflect" 6 | "strings" 7 | ) 8 | 9 | // TypeStub fulfills the reflect.Type interface, but only knows the fully qualified 10 | // name of a type. All other details will panic upon use. 11 | type TypeStub struct { 12 | reflect.Type 13 | pkgName string 14 | typeName string 15 | } 16 | 17 | // NewTypeStub creates a new reflect.Type stub based on a package and a type. 18 | func NewTypeStub(packagePath string, typeName string) (*TypeStub, error) { 19 | return &TypeStub{ 20 | pkgName: packagePath, 21 | typeName: typeName, 22 | }, nil 23 | } 24 | 25 | // NewTypeStubIdentifier creates a new reflect.Type stub based on a fully-qualified 26 | // Go type name. The expected format of the identifier is: 27 | // . 28 | // 29 | // For example: 30 | // ``` 31 | // github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.StorageBlobCreatedEventData 32 | // ``` 33 | func NewTypeStubIdentifier(identifier string) (*TypeStub, error) { 34 | last := strings.LastIndex(identifier, ".") 35 | if last < 0 { 36 | return nil, errors.New("no type found") 37 | } 38 | 39 | return &TypeStub{ 40 | pkgName: identifier[:last], 41 | typeName: identifier[last+1:], 42 | }, nil 43 | } 44 | 45 | // Name fetches the type's name within the package. 46 | func (t *TypeStub) Name() string { 47 | return t.typeName 48 | } 49 | 50 | // PkgPath fetches the package's unique identifier. 51 | func (t *TypeStub) PkgPath() string { 52 | return t.pkgName 53 | } 54 | -------------------------------------------------------------------------------- /cmd/eventgrid_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import "testing" 4 | 5 | func TestParseEventArg(t *testing.T) { 6 | testCases := []struct { 7 | input string 8 | expectedEventType string 9 | expectedGoType string 10 | expectedErr error 11 | }{ 12 | {"a:b", "a", "b", nil}, 13 | {"a:b:c", "a:b", "c", nil}, 14 | { 15 | "Microsoft.Storage.BlobCreated:github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.StorageBlobCreatedEventData", 16 | "Microsoft.Storage.BlobCreated", 17 | "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.StorageBlobCreatedEventData", 18 | nil, 19 | }, 20 | { 21 | "Microsoft.Storage.BlobCreated", 22 | "Microsoft.Storage.BlobCreated", 23 | wellKnownEvents["Microsoft.Storage.BlobCreated"], 24 | nil, 25 | }, 26 | { 27 | "Microsoft.Storage.BlobCreated:github.com/marstr/playground.Blob", 28 | "Microsoft.Storage.BlobCreated", 29 | "github.com/marstr/playground.Blob", 30 | nil, 31 | }, 32 | } 33 | 34 | for _, tc := range testCases { 35 | t.Run(tc.input, func(t *testing.T) { 36 | eventType, goType, err := parseEventArg(tc.input) 37 | if err != tc.expectedErr { 38 | t.Logf("\ngot:\n\t%v\nwant:\n\t%v", err, tc.expectedErr) 39 | t.Fail() 40 | } 41 | 42 | if eventType != tc.expectedEventType { 43 | t.Logf("\ngot:\n\t%s\nwant:\n\t%s", eventType, tc.expectedEventType) 44 | t.Fail() 45 | } 46 | 47 | if goType != tc.expectedGoType { 48 | t.Logf("\ngot:\n\t%s\nwant:\n\t%s", eventType, tc.expectedGoType) 49 | t.Fail() 50 | } 51 | }) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /generators/eventgrid/templates/actions/eventgrid_name.go.tmpl: -------------------------------------------------------------------------------- 1 | package actions 2 | 3 | import ( 4 | {{ range $i := .imports }} {{$i}} 5 | {{ end }} 6 | ) 7 | 8 | // My{{$.name.Camel}}Subscriber gathers responds to all Requests sent to a particular endpoint. 9 | type {{$.name.Camel}}Subscriber struct { 10 | eg.Subscriber 11 | } 12 | 13 | // New{{$.name.Camel}}Subscriber instantiates {{$.name.Camel}}Subscriber for use in a `buffalo.App`. 14 | func New{{$.name.Camel}}Subscriber(parent eg.Subscriber) (created *{{$.name.Camel}}Subscriber) { 15 | dispatcher := eg.NewTypeDispatchSubscriber(parent) 16 | 17 | created = &{{$.name.Camel}}Subscriber{ 18 | Subscriber: dispatcher, 19 | } 20 | 21 | {{ range $t := .types}} 22 | dispatcher.Bind("{{$t.Identifier}}", created.Receive{{$t.Name.Camel}}) 23 | {{end}} 24 | dispatcher.Bind(eg.EventTypeWildcard, created.ReceiveDefault) 25 | 26 | return 27 | } 28 | 29 | {{ range $t := .types }} 30 | // Receive{{$t.Name.Camel}} will respond to an `eventgrid.Event` carrying a serialized `{{$t.Name.Camel}}` as its payload. 31 | func (s *{{$.name.Camel}}Subscriber) Receive{{$t.Name.Camel}}(c buffalo.Context, e eg.Event) error { 32 | var payload {{$t.PkgSpec}}.{{$t.Name.Camel}} 33 | if err := json.Unmarshal(e.Data, &payload); err != nil { 34 | return c.Error(http.StatusBadRequest, errors.New("unable to unmarshal request data")) 35 | } 36 | 37 | // Replace the code below with your logic 38 | return c.Error(http.StatusInternalServerError, errors.New("not implemented")) 39 | } 40 | {{end}} 41 | 42 | // ReceiveDefault will respond to an `eventgrid.Event` carrying any EventType as its payload. 43 | func (s *{{$.name.Camel}}Subscriber) ReceiveDefault(c buffalo.Context, e eg.Event) error { 44 | return c.Error(http.StatusInternalServerError, errors.New("not implemented")) 45 | } 46 | -------------------------------------------------------------------------------- /sdk/eventgrid/type_dispatch_subscriber_test.go: -------------------------------------------------------------------------------- 1 | package eventgrid_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "net/http" 7 | 8 | "github.com/Azure/buffalo-azure/sdk/eventgrid" 9 | "github.com/gobuffalo/buffalo" 10 | ) 11 | 12 | func ExampleTypeDispatchSubscriber_Receive() { 13 | var mySubscriber eventgrid.Subscriber 14 | mySubscriber = eventgrid.BaseSubscriber{} 15 | mySubscriber = eventgrid.NewTypeDispatchSubscriber(mySubscriber).Bind("Microsoft.Storage.BlobCreated", func(c buffalo.Context, e eventgrid.Event) (err error) { 16 | _, err = fmt.Println(e.ID) 17 | return 18 | }) 19 | 20 | req, err := http.NewRequest(http.MethodPost, "localhost", bytes.NewReader([]byte(`[{ 21 | "topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount", 22 | "subject": "/blobServices/default/containers/oc2d2817345i200097container/blobs/oc2d2817345i20002296blob", 23 | "eventType": "Microsoft.Storage.BlobCreated", 24 | "eventTime": "2017-06-26T18:41:00.9584103Z", 25 | "id": "831e1650-001e-001b-66ab-eeb76e069631", 26 | "data": { 27 | "api": "PutBlockList", 28 | "clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760", 29 | "requestId": "831e1650-001e-001b-66ab-eeb76e000000", 30 | "eTag": "0x8D4BCC2E4835CD0", 31 | "contentType": "application/octet-stream", 32 | "contentLength": 524288, 33 | "blobType": "BlockBlob", 34 | "url": "https://oc2d2817345i60006.blob.core.windows.net/oc2d2817345i200097container/oc2d2817345i20002296blob", 35 | "sequencer": "00000000000004420000000000028963", 36 | "storageDiagnostics": { 37 | "batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0" 38 | } 39 | }, 40 | "dataVersion": "", 41 | "metadataVersion": "1" 42 | }]`))) 43 | if err != nil { 44 | fmt.Println(err) 45 | return 46 | } 47 | req.Header.Add("Content-Type", "application/json") 48 | 49 | ctx := NewMockContext(req) 50 | 51 | err = mySubscriber.Receive(ctx) 52 | if err != nil { 53 | fmt.Println(err) 54 | return 55 | } 56 | 57 | // Output: 831e1650-001e-001b-66ab-eeb76e069631 58 | } 59 | -------------------------------------------------------------------------------- /generators/eventgrid/builder/builder_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "io/ioutil" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestReadFiles(t *testing.T) { 11 | discoveredFiles := make(chan file) 12 | 13 | currentDirectory, err := ioutil.ReadDir(".") 14 | if err != nil { 15 | t.Error(err) 16 | return 17 | } 18 | 19 | expected := map[string]struct{}{} 20 | 21 | for _, item := range currentDirectory { 22 | expected[item.Name()] = struct{}{} 23 | } 24 | 25 | finishedCheckingExpected := make(chan struct{}) 26 | go func(ctx context.Context, discoveredFiles <-chan file) { 27 | defer close(finishedCheckingExpected) 28 | for { 29 | select { 30 | case discovered, ok := <-discoveredFiles: 31 | if !ok { 32 | return 33 | } 34 | if _, ok = expected[discovered.path]; ok { 35 | delete(expected, discovered.path) 36 | } else { 37 | t.Logf("unexpected file: %q", discovered.path) 38 | t.Fail() 39 | } 40 | case <-ctx.Done(): 41 | return 42 | } 43 | } 44 | }(context.Background(), discoveredFiles) 45 | 46 | if err := readFiles(context.Background(), ".", discoveredFiles); err != nil { 47 | t.Error(err) 48 | return 49 | } 50 | close(discoveredFiles) 51 | 52 | <-finishedCheckingExpected 53 | 54 | for unfound := range expected { 55 | t.Logf("expected %q, but it was not found", unfound) 56 | t.Fail() 57 | } 58 | } 59 | 60 | func TestReadFiles_Cancel(t *testing.T) { 61 | testControl, testCancel := context.WithTimeout(context.Background(), 6*time.Second) 62 | defer testCancel() 63 | 64 | ctx, cancel := context.WithCancel(testControl) 65 | discoveredFiles := make(chan file) 66 | defer close(discoveredFiles) 67 | 68 | finished := make(chan error) 69 | go func(ctx context.Context, err chan<- error) { 70 | err <- readFiles(ctx, ".", discoveredFiles) 71 | }(ctx, finished) 72 | cancel() 73 | 74 | select { 75 | case <-testControl.Done(): 76 | t.Log("test timed out") 77 | t.Fail() 78 | case err := <-finished: 79 | if err != context.Canceled { 80 | t.Logf("got: %v wanted: %v", err, context.Canceled) 81 | t.Fail() 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /sdk/eventgrid/cache_test.go: -------------------------------------------------------------------------------- 1 | package eventgrid_test 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "testing" 7 | "time" 8 | 9 | "github.com/Azure/buffalo-azure/sdk/eventgrid" 10 | ) 11 | 12 | func ExampleCache() { 13 | myCache := &eventgrid.Cache{} 14 | 15 | myCache.Add(eventgrid.Event{ 16 | EventType: "Contoso.Buffalo.CacheProd", 17 | }) 18 | myCache.Add(eventgrid.Event{ 19 | EventType: "Microsoft.Storage.BlobCreated", 20 | }) 21 | 22 | fmt.Println(myCache.List()) 23 | 24 | myCache.Clear() 25 | fmt.Println(myCache.List()) 26 | // Output: 27 | // [{ [] Microsoft.Storage.BlobCreated } { [] Contoso.Buffalo.CacheProd }] 28 | // [] 29 | } 30 | 31 | func ExampleCache_SetTTL() { 32 | myCache := &eventgrid.Cache{} 33 | myCache.SetTTL(time.Second) 34 | 35 | myCache.Add(eventgrid.Event{ 36 | EventType: "Microsoft.Storage.BlobCreated", 37 | }) 38 | fmt.Println(len(myCache.List())) 39 | 40 | <-time.After(2 * time.Second) 41 | fmt.Println(len(myCache.List())) 42 | 43 | // Output: 44 | // 1 45 | // 0 46 | } 47 | 48 | func ExampleCache_SetMaxDepth() { 49 | myCache := &eventgrid.Cache{} 50 | myCache.SetMaxDepth(2) 51 | 52 | fmt.Println(len(myCache.List())) 53 | myCache.Add(eventgrid.Event{}) 54 | fmt.Println(len(myCache.List())) 55 | myCache.Add(eventgrid.Event{}) 56 | fmt.Println(len(myCache.List())) 57 | myCache.Add(eventgrid.Event{}) 58 | fmt.Println(len(myCache.List())) 59 | 60 | // Output: 61 | // 0 62 | // 1 63 | // 2 64 | // 2 65 | } 66 | 67 | func TestCache_SetMaxDepth(t *testing.T) { 68 | 69 | myCache := &eventgrid.Cache{} 70 | myCache.SetMaxDepth(2) 71 | 72 | output := &bytes.Buffer{} 73 | 74 | fmt.Fprint(output, len(myCache.List())) 75 | myCache.Add(eventgrid.Event{}) 76 | fmt.Fprint(output, len(myCache.List())) 77 | myCache.Add(eventgrid.Event{}) 78 | fmt.Fprint(output, len(myCache.List())) 79 | myCache.Add(eventgrid.Event{}) 80 | fmt.Fprint(output, len(myCache.List())) 81 | fmt.Fprint(output, len(myCache.List())) 82 | fmt.Fprint(output, len(myCache.List())) 83 | 84 | const want = "012222" 85 | if got := output.String(); got != want { 86 | t.Logf("got: %q want: %q", got, want) 87 | t.Fail() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /generators/eventgrid/generator_test.go: -------------------------------------------------------------------------------- 1 | // +build !short 2 | 3 | package eventgrid 4 | 5 | import ( 6 | "context" 7 | "io" 8 | "io/ioutil" 9 | "os" 10 | "os/exec" 11 | "path" 12 | "path/filepath" 13 | "reflect" 14 | "testing" 15 | "time" 16 | 17 | "github.com/Azure/buffalo-azure/sdk/eventgrid" 18 | "github.com/gobuffalo/buffalo/meta" 19 | ) 20 | 21 | func TestGenerator_Run(t *testing.T) { 22 | const bufCmd, depCmd = "buffalo", "dep" 23 | requiredTools := []string{bufCmd, depCmd, "go", "node"} 24 | 25 | const appName = "gentest" 26 | for _, tool := range requiredTools { 27 | if _, err := exec.LookPath(tool); err != nil { 28 | t.Skipf("%s not found on system", tool) 29 | return 30 | } 31 | } 32 | 33 | subject := Generator{} 34 | 35 | testLoc := path.Join(os.Getenv("GOPATH"), "src") 36 | 37 | loc, err := ioutil.TempDir(testLoc, "buffalo-azure_eventgrid_test") 38 | if err != nil { 39 | t.Error(err) 40 | return 41 | } 42 | defer os.RemoveAll(loc) 43 | t.Log("Output Location: ", loc) 44 | 45 | ctx, cancel := context.WithTimeout(context.Background(), 45*time.Minute) 46 | defer cancel() 47 | 48 | var outHandle, errHandle io.Writer 49 | outHandle, err = os.Create(path.Join(loc, "buffalo_stdout.txt")) 50 | if err != nil { 51 | t.Logf("not able to harness %s stdout", bufCmd) 52 | outHandle = ioutil.Discard 53 | } 54 | errHandle, err = os.Create(path.Join(loc, "buffalo_stderr.txt")) 55 | if err != nil { 56 | t.Logf("not able to harness %s stderr", bufCmd) 57 | errHandle = ioutil.Discard 58 | } 59 | 60 | bufCreater := exec.CommandContext(ctx, bufCmd, "new", appName, "--with-dep") 61 | bufCreater.Dir = loc 62 | bufCreater.Stdout = outHandle 63 | bufCreater.Stderr = errHandle 64 | if err := bufCreater.Run(); err != nil { 65 | t.Error(err) 66 | return 67 | } 68 | 69 | fakeApp := meta.App{ 70 | Root: filepath.Join(loc, appName), 71 | ActionsPkg: "github.com/marstr/musicvotes/actions", 72 | } 73 | 74 | faux := eventgrid.SubscriptionValidationRequest{} 75 | 76 | if err = subject.Run(fakeApp, "ingress", map[string]reflect.Type{ 77 | "Microsoft.EventGrid.SubscriptionValidation": reflect.TypeOf(faux), 78 | }); err != nil { 79 | t.Error(err) 80 | return 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /cmd/version.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "fmt" 25 | "runtime" 26 | 27 | "github.com/spf13/cobra" 28 | ) 29 | 30 | var version string 31 | 32 | // versionCmd represents the version command 33 | var versionCmd = &cobra.Command{ 34 | Use: "version", 35 | Short: "Print version information about the Buffalo-Azure plugin.", 36 | Run: func(cmd *cobra.Command, args []string) { 37 | if version == "" { 38 | version = "unknown version" 39 | } 40 | fmt.Println("Buffalo-Azure Version: ", version) 41 | fmt.Println("Go version: ", runtime.Version()) 42 | }, 43 | Args: cobra.NoArgs, 44 | } 45 | 46 | func init() { 47 | azureCmd.AddCommand(versionCmd) 48 | 49 | // Here you will define your flags and configuration settings. 50 | 51 | // Cobra supports Persistent Flags which will work for this command 52 | // and all subcommands, e.g.: 53 | // versionCmd.PersistentFlags().String("foo", "", "A help for foo") 54 | 55 | // Cobra supports local flags which will only run when this command 56 | // is called directly, e.g.: 57 | // versionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 58 | } 59 | -------------------------------------------------------------------------------- /sdk/eventgrid/subscription_validation.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | 9 | "github.com/gobuffalo/buffalo" 10 | "github.com/gobuffalo/uuid" 11 | ) 12 | 13 | // SubscriptionValidationRequest allows for easy unmarshaling of the first 14 | // event sent by an Event Grid Topic. 15 | type SubscriptionValidationRequest struct { 16 | ValidationCode uuid.UUID `json:"validationCode,omitempty"` 17 | } 18 | 19 | // SubscriptionValidationMiddleware provides a `buffalo.Handler` which will triage all incoming requests 20 | // to either submit it for event processing, or echo back the response the server expects to validate a 21 | // subscription. 22 | func SubscriptionValidationMiddleware(next buffalo.Handler) buffalo.Handler { 23 | return func(c buffalo.Context) error { 24 | if typeHeader := c.Request().Header.Get("Aeg-Event-Type"); strings.EqualFold(typeHeader, "SubscriptionValidation") { 25 | var events []Event 26 | if err := c.Bind(&events); err != nil { 27 | return c.Error(http.StatusBadRequest, err) 28 | } 29 | 30 | if numEvents := len(events); numEvents != 1 { 31 | return c.Error(http.StatusBadRequest, fmt.Errorf("expected exactly 1 event, got %d", numEvents)) 32 | } 33 | 34 | return ReceiveSubscriptionValidationRequest(c, events[0]) 35 | } 36 | return next(c) 37 | } 38 | } 39 | 40 | // ReceiveSubscriptionValidationRequest will echo the ValidateCode sent in the request 41 | // back to the Event Grid Topic seeking subscription validation. 42 | func ReceiveSubscriptionValidationRequest(c buffalo.Context, e Event) error { 43 | var svr SubscriptionValidationRequest 44 | err := json.Unmarshal(e.Data, &svr) 45 | if err != nil { 46 | return c.Error(http.StatusBadRequest, err) 47 | } 48 | 49 | type SubscriptionValidationResponse struct { 50 | ValidationCode uuid.UUID `json:"validationResponse,omitempty"` 51 | } 52 | 53 | if logger := c.Logger(); logger != nil { 54 | logger.Info("received validation request from: ", c.Request().RemoteAddr) 55 | } 56 | 57 | enc := json.NewEncoder(c.Response()) 58 | 59 | err = enc.Encode(&SubscriptionValidationResponse{svr.ValidationCode}) 60 | if err != nil { 61 | return c.Error(http.StatusInternalServerError, err) 62 | } 63 | 64 | c.Response().WriteHeader(http.StatusOK) 65 | return nil 66 | } 67 | -------------------------------------------------------------------------------- /cmd/azure.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "github.com/spf13/cobra" 25 | ) 26 | 27 | // azureCmd represents the azure command 28 | var azureCmd = &cobra.Command{ 29 | Use: "azure", 30 | Aliases: []string{"az"}, 31 | Short: "Exposes tools for more easily using Azure services.", 32 | Long: `Updates your application to get started with Azure services. 33 | 34 | The most common types of operations are: 35 | - Adding configuration to host your application in Azure. 36 | - Updating routes in your application to do handshakes with Azure services.`, 37 | // Run: func(cmd *cobra.Command, args []string) { 38 | // fmt.Println("azure called") 39 | // }, 40 | } 41 | 42 | func init() { 43 | rootCmd.AddCommand(azureCmd) 44 | 45 | // Here you will define your flags and configuration settings. 46 | 47 | // Cobra supports Persistent Flags which will work for this command 48 | // and all subcommands, e.g.: 49 | // azureCmd.PersistentFlags().String("foo", "", "A help for foo") 50 | 51 | // Cobra supports local flags which will only run when this command 52 | // is called directly, e.g.: 53 | // azureCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 54 | } 55 | -------------------------------------------------------------------------------- /cmd/available.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "encoding/json" 25 | "os" 26 | 27 | "github.com/gobuffalo/buffalo-plugins/plugins" 28 | 29 | "github.com/spf13/cobra" 30 | ) 31 | 32 | // availableCmd represents the available command 33 | var availableCmd = &cobra.Command{ 34 | Use: "available", 35 | Short: "Describes the supported gobuffalo.io commands.", 36 | Run: func(cmd *cobra.Command, args []string) { 37 | usable := plugins.Commands{ 38 | {Name: azureCmd.Name(), BuffaloCommand: "root", Description: azureCmd.Short}, 39 | {Name: eventgridCmd.Name(), BuffaloCommand: "generate", Description: eventgridCmd.Short}, 40 | } 41 | 42 | err := json.NewEncoder(os.Stdout).Encode(usable) 43 | if err != nil { 44 | os.Exit(1) 45 | } 46 | }, 47 | } 48 | 49 | func init() { 50 | rootCmd.AddCommand(availableCmd) 51 | 52 | // Here you will define your flags and configuration settings. 53 | 54 | // Cobra supports Persistent Flags which will work for this command 55 | // and all subcommands, e.g.: 56 | // availableCmd.PersistentFlags().String("foo", "", "A help for foo") 57 | 58 | // Cobra supports local flags which will only run when this command 59 | // is called directly, e.g.: 60 | // availableCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 61 | } 62 | -------------------------------------------------------------------------------- /generators/eventgrid/generator.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "fmt" 5 | "path" 6 | "path/filepath" 7 | "reflect" 8 | "sort" 9 | 10 | "github.com/gobuffalo/buffalo/generators" 11 | "github.com/gobuffalo/buffalo/meta" 12 | "github.com/gobuffalo/makr" 13 | "github.com/markbates/inflect" 14 | 15 | "github.com/Azure/buffalo-azure/generators/common" 16 | ) 17 | 18 | //go:generate go run ./builder/builder.go -o ./static_templates.go ./templates 19 | 20 | // Generator will parse an existing `buffalo.App` and add the relevant code 21 | // to make that application be ready for being subscribed to an Event Grid Topic. 22 | type Generator struct{} 23 | 24 | // Run executes the Generator's main purpose, of extending a Buffalo application 25 | // to listen for Event Grid Events. 26 | func (eg *Generator) Run(app meta.App, name string, types map[string]reflect.Type) error { 27 | iName := inflect.Name(name) 28 | type TypeMapping struct { 29 | Identifier string 30 | inflect.Name 31 | PkgPath string 32 | PkgSpec common.PackageSpecifier 33 | } 34 | flatTypes := make([]TypeMapping, 0, len(types)) 35 | 36 | ib := common.NewImportBag() 37 | ib.AddImport("encoding/json") 38 | ib.AddImport("errors") 39 | ib.AddImport("net/http") 40 | ib.AddImportWithSpecifier("github.com/Azure/buffalo-azure/sdk/eventgrid", "eg") 41 | ib.AddImport("github.com/gobuffalo/buffalo") 42 | 43 | for i, n := range types { 44 | flatTypes = append(flatTypes, TypeMapping{ 45 | Identifier: i, 46 | PkgPath: n.PkgPath(), 47 | PkgSpec: ib.AddImport(common.PackagePath(n.PkgPath())), 48 | Name: inflect.Name(n.Name()), 49 | }) 50 | } 51 | 52 | // I <3 determinism 53 | sort.Slice(flatTypes, func(i, j int) bool { 54 | return flatTypes[i].Identifier < flatTypes[j].Identifier 55 | }) 56 | 57 | eventgridFilepath := filepath.Join(path.Base(app.ActionsPkg), fmt.Sprintf("%s.go", iName.File())) 58 | g := makr.New() 59 | defer g.Fmt(app.Root) 60 | 61 | g.Add(makr.NewFile(eventgridFilepath, string(staticTemplates["templates/actions/eventgrid_name.go.tmpl"]))) 62 | g.Add(&makr.Func{ 63 | Should: func(_ makr.Data) bool { return true }, 64 | Runner: func(root string, data makr.Data) error { 65 | subName := data["name"].(inflect.Name) 66 | registrationExpr := fmt.Sprintf(`eventgrid.RegisterSubscriber(app, "/%s", New%sSubscriber(&eventgrid.BaseSubscriber{}))`, subName.Lower(), subName.Camel()) 67 | return generators.AddInsideAppBlock(registrationExpr) 68 | }, 69 | }) 70 | 71 | d := make(makr.Data) 72 | d["name"] = iName 73 | d["types"] = flatTypes 74 | d["imports"] = ib.List() 75 | 76 | return g.Run(app.Root, d) 77 | } 78 | -------------------------------------------------------------------------------- /sdk/eventgrid/context_test.go: -------------------------------------------------------------------------------- 1 | package eventgrid_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "net/http" 10 | "sync" 11 | "testing" 12 | 13 | "github.com/Azure/buffalo-azure/sdk/eventgrid" 14 | "github.com/gobuffalo/buffalo" 15 | ) 16 | 17 | func ExampleContext() { 18 | ctx := eventgrid.NewContext(&buffalo.DefaultContext{}) 19 | 20 | var wg sync.WaitGroup 21 | 22 | succeed := func(c buffalo.Context) error { 23 | defer wg.Done() 24 | 25 | c.Response().WriteHeader(http.StatusOK) 26 | 27 | return nil 28 | } 29 | 30 | fail := func(c buffalo.Context) error { 31 | defer wg.Done() 32 | return c.Error(http.StatusInternalServerError, errors.New("unknown error")) 33 | } 34 | 35 | wg.Add(3) 36 | go succeed(ctx) 37 | go fail(ctx) 38 | go succeed(ctx) 39 | wg.Wait() 40 | 41 | fmt.Println(ctx.ResponseHasFailure()) 42 | 43 | // Output: true 44 | } 45 | 46 | func TestContext_ResponseWriter_Mask(t *testing.T) { 47 | var outer buffalo.Context = &buffalo.DefaultContext{} 48 | inner := eventgrid.NewContext(outer) 49 | 50 | if inner.Response() == outer.Response() { 51 | t.Fail() 52 | } 53 | } 54 | 55 | type MockContext struct { 56 | buffalo.Context 57 | request *http.Request 58 | *MockResponseWriter 59 | } 60 | 61 | func NewMockContext(req *http.Request) *MockContext { 62 | return &MockContext{ 63 | Context: &buffalo.DefaultContext{}, 64 | request: req, 65 | MockResponseWriter: NewMockResponseWriter(), 66 | } 67 | } 68 | 69 | func (c MockContext) Request() *http.Request { 70 | return c.request 71 | } 72 | 73 | func (c MockContext) Response() http.ResponseWriter { 74 | return c.MockResponseWriter 75 | } 76 | 77 | func (c MockContext) Bind(payload interface{}) error { 78 | return json.NewDecoder(c.Request().Body).Decode(payload) 79 | } 80 | 81 | type MockResponseWriter struct { 82 | status int 83 | header http.Header 84 | body *bytes.Buffer 85 | } 86 | 87 | func NewMockResponseWriter() *MockResponseWriter { 88 | return &MockResponseWriter{ 89 | body: bytes.NewBuffer([]byte{}), 90 | header: make(http.Header), 91 | } 92 | } 93 | 94 | func (w *MockResponseWriter) Header() http.Header { 95 | return w.header 96 | } 97 | 98 | func (w *MockResponseWriter) Write(d []byte) (int, error) { 99 | return w.body.Write(d) 100 | } 101 | 102 | func (w *MockResponseWriter) WriteHeader(s int) { 103 | w.status = s 104 | } 105 | 106 | func (w *MockResponseWriter) Body() io.Reader { 107 | return w.body 108 | } 109 | 110 | func (w *MockResponseWriter) Status() int { 111 | return w.status 112 | } 113 | -------------------------------------------------------------------------------- /sdk/eventgrid/subscriber.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "encoding/json" 5 | "errors" 6 | "net/http" 7 | 8 | "github.com/gobuffalo/buffalo" 9 | ) 10 | 11 | // MaxPayloadSize is the largest number of bytes permitted in the body of 12 | // a request sent to an Event Grid Topic. 13 | const MaxPayloadSize = 1024 * 1024 14 | 15 | // MaxEventSize is the largest number of bytes permitted for an individual 16 | // Event Grid Event. 17 | const MaxEventSize = 64 * 1024 18 | 19 | // Event allows for easy processing of Event Grid Events. 20 | // 21 | // External documentation on Event Grid Events can be found here: 22 | // https://docs.microsoft.com/en-us/azure/event-grid/event-schema 23 | type Event struct { 24 | ID string `json:"id"` 25 | Topic string `json:"topic"` 26 | Subject string `json:"subject"` 27 | Data json.RawMessage `json:"data"` 28 | EventType string `json:"eventType"` 29 | EventTime string `json:"eventTime"` 30 | MetadataVersion string `json:"metadataVersion"` 31 | DataVersion string `json:"dataVersion"` 32 | } 33 | 34 | // UnmarshalData attempts to read the value associated with the "data" property 35 | // into the value pointed to by v. 36 | func (e Event) UnmarshalData(v interface{}) error { 37 | return json.Unmarshal(e.Data, v) 38 | } 39 | 40 | // EventHandler extends the definition of buffalo.Handler to include 41 | // an `Event`. 42 | type EventHandler func(buffalo.Context, Event) error 43 | 44 | // Subscriber allows for quick implementation of RESTful actions while 45 | // working with Event Grid events. 46 | type Subscriber interface { 47 | List(buffalo.Context) error 48 | New(buffalo.Context) error 49 | Receive(buffalo.Context) error 50 | Show(buffalo.Context) error 51 | } 52 | 53 | // BaseSubscriber will always respond to request by returning an HTTP 404 status. 54 | type BaseSubscriber struct{} 55 | 56 | // List responds with an HTTP NotFound Status Code. 57 | func (s BaseSubscriber) List(c buffalo.Context) error { 58 | return c.Error(http.StatusNotFound, errors.New("subscriber not implemented")) 59 | } 60 | 61 | // New responds with an HTTP NotFound Status Code. 62 | func (s BaseSubscriber) New(c buffalo.Context) error { 63 | return c.Error(http.StatusNotFound, errors.New("subscriber not implemented")) 64 | } 65 | 66 | // Receive responds with an HTTP NotFound Status Code. 67 | func (s BaseSubscriber) Receive(c buffalo.Context) error { 68 | return c.Error(http.StatusNotFound, errors.New("subscriber not implemented")) 69 | } 70 | 71 | // Show responds with an HTTP NotFound Status Code. 72 | func (s BaseSubscriber) Show(c buffalo.Context) error { 73 | return c.Error(http.StatusNotFound, errors.New("subscriber not implemented")) 74 | } 75 | -------------------------------------------------------------------------------- /cmd/deployment_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "os" 7 | "testing" 8 | ) 9 | 10 | func TestDeploymentParameters_UnmarshalFile(t *testing.T) { 11 | handle, err := os.Open("./testdata/parameters1.json") 12 | if err != nil { 13 | t.Error(err) 14 | return 15 | } 16 | defer handle.Close() 17 | 18 | subject := NewDeploymentParameters() 19 | dec := json.NewDecoder(handle) 20 | dec.Decode(subject) 21 | 22 | if got, want := len(subject.Parameters), 5; got != want { 23 | t.Logf("Number Parameters:\n\tgot: %d want: %d", got, want) 24 | t.Fail() 25 | } 26 | 27 | expected := map[string]string{ 28 | "database": "postgresql", 29 | "databaseAdministratorLogin": "buffaloAdmin", 30 | "databaseName": "buffalo30_development", 31 | "imageName": "marstr/quickstart:latest", 32 | "name": "buffalo-app-uqqq1nfno1", 33 | } 34 | 35 | for k, v := range expected { 36 | if val, ok := subject.Parameters[k]; ok { 37 | if v != val.Value.(string) { 38 | t.Logf("got: %q want: %q", val, v) 39 | t.Fail() 40 | } 41 | } else { 42 | t.Logf("didn't find expected parameter %q", k) 43 | t.Fail() 44 | } 45 | } 46 | } 47 | 48 | func TestDeploymentParameters_MarshalRoundTrip(t *testing.T) { 49 | subject := NewDeploymentParameters() 50 | 51 | subject.Parameters["foo"] = DeploymentParameter{"bar"} 52 | subject.Parameters["bar"] = DeploymentParameter{2} 53 | 54 | buf := bytes.NewBuffer([]byte{}) 55 | 56 | enc := json.NewEncoder(buf) 57 | if err := enc.Encode(subject); err != nil { 58 | t.Error(err) 59 | return 60 | } 61 | 62 | rehydrated := NewDeploymentParameters() 63 | dec := json.NewDecoder(buf) 64 | if err := dec.Decode(rehydrated); err != nil { 65 | t.Error(err) 66 | return 67 | } 68 | 69 | if rehydrated.ContentVersion != subject.ContentVersion { 70 | t.Logf("Unexpected Content Version:\n\tgot: %q\n\twant: %q", rehydrated.ContentVersion, subject.ContentVersion) 71 | t.Fail() 72 | } 73 | 74 | if rehydrated.Schema != subject.Schema { 75 | t.Logf("Unexpected Content Version:\n\tgot: %q\n\twant: %q", rehydrated.Schema, subject.Schema) 76 | t.Fail() 77 | } 78 | 79 | if len(rehydrated.Parameters) != len(subject.Parameters) { 80 | t.Logf("parameter count:\n\tgot: %d want: %d", len(rehydrated.Parameters), len(subject.Parameters)) 81 | t.Fail() 82 | } 83 | 84 | for k, v := range rehydrated.Parameters { 85 | if orig, ok := subject.Parameters[k]; ok { 86 | t.Logf("Found %q -> %v : %v", k, orig, v) 87 | delete(subject.Parameters, k) 88 | } else { 89 | t.Logf("unexpected parameter: %q included with value: %v", k, v) 90 | t.Fail() 91 | } 92 | } 93 | 94 | for k := range subject.Parameters { 95 | t.Logf("Missing parameter: %q", k) 96 | t.Fail() 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/opensource/security/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/opensource/security/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/opensource/security/pgpkey). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://aka.ms/opensource/security/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/opensource/security/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/opensource/security/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /sdk/eventgrid/cache.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "sync" 5 | "time" 6 | ) 7 | 8 | // CacheDefaultMaxDepth is the maximum number of Events that will 9 | // be stored here, before they begin automatically removed. 10 | const CacheDefaultMaxDepth uint = 100000 11 | 12 | // CacheDefaultTTL is the default length of time that each event will live 13 | // in the cache before it is aut 14 | const CacheDefaultTTL = time.Hour * 48 15 | 16 | // Cache will hold a set number of events for a short amount of time. 17 | type Cache struct { 18 | sync.RWMutex 19 | maxDepth uint 20 | ttl time.Duration 21 | root *cacheNode 22 | } 23 | 24 | // MaxDepth gets the largest number of `Event` instances that this `Cache` 25 | // will hold before automatically deleting the least recently arriving ones. 26 | func (c *Cache) MaxDepth() uint { 27 | c.RLock() 28 | defer c.RUnlock() 29 | 30 | return c._MaxDepth() 31 | } 32 | 33 | func (c *Cache) _MaxDepth() uint { 34 | if c.maxDepth == 0 { 35 | return CacheDefaultMaxDepth 36 | } 37 | return c.maxDepth 38 | } 39 | 40 | // SetMaxDepth changes the largest number of `Event` instances that this `Cache`. 41 | // will hold. 42 | func (c *Cache) SetMaxDepth(depth uint) { 43 | c.Lock() 44 | defer c.Unlock() 45 | 46 | c.maxDepth = depth 47 | } 48 | 49 | // TTL get the amount of time each event will last before being cleared from the `Cache`. 50 | func (c *Cache) TTL() time.Duration { 51 | c.RLock() 52 | defer c.RUnlock() 53 | 54 | return c._TTL() 55 | } 56 | 57 | func (c *Cache) _TTL() time.Duration { 58 | if c.ttl <= 0 { 59 | return CacheDefaultTTL 60 | } 61 | return c.ttl 62 | } 63 | 64 | // SetTTL sets the amount of time each event will last before being cleared from the `Cache`. 65 | func (c *Cache) SetTTL(d time.Duration) { 66 | c.Lock() 67 | defer c.Unlock() 68 | 69 | c.ttl = d 70 | } 71 | 72 | // Add creates an entry in the `Cache`. 73 | func (c *Cache) Add(e Event) { 74 | c.Lock() 75 | defer c.Unlock() 76 | 77 | created := &cacheNode{ 78 | Event: e, 79 | next: c.root, 80 | expiration: time.Now().Add(c._TTL()), 81 | } 82 | 83 | c.root = created 84 | } 85 | 86 | // Clear removes all entries from the Event Cache. 87 | func (c *Cache) Clear() { 88 | c.Lock() 89 | defer c.Unlock() 90 | 91 | c.root = nil 92 | } 93 | 94 | // List reads all of the Events in the cache at a particular moment. 95 | func (c *Cache) List() (results []Event) { 96 | c.RLock() 97 | defer c.RUnlock() 98 | 99 | called := time.Now() 100 | 101 | prev := c.root 102 | i := uint(0) 103 | 104 | for current := c.root; current != nil; current = current.next { 105 | if i >= c._MaxDepth() { 106 | current.next = nil 107 | break 108 | } 109 | 110 | if current.expiration.After(called) { 111 | results = append(results, current.Event) 112 | i++ 113 | } else { 114 | prev.next = current.next 115 | } 116 | } 117 | return 118 | } 119 | 120 | type cacheNode struct { 121 | Event 122 | next *cacheNode 123 | expiration time.Time 124 | } 125 | -------------------------------------------------------------------------------- /documentation/deployment/Deployment.LocalDockerBuild.md: -------------------------------------------------------------------------------- 1 | ## Deploy Using Docker from your Local Machine 2 | 3 | #### Pre-Requisites 4 | 5 | For these instructions to be relevant to your Buffalo-Application you must: 6 | - Both Buffalo and Buffalo-Azure should be available in the PATH of your local machine. If you don't have them you can 7 | get started here: 8 | - Buffalo: https://gobuffalo.io/en/docs/installation 9 | - Buffalo-Azure: [Installing Buffalo Azure](./README.md#Installation) 10 | - Have `docker` installed on your local machine. You can find a download here: https://www.docker.com/get-docker 11 | - If you're using Windows or Linux, Virtualization will have to be enabled on your machine. If it is not enabled, 12 | you'll have to reboot and configure your system settings in the BIOS. 13 | - Your Buffalo Application should have a "Dockerfile" in the root of the repository. 14 | - A Docker repository exists to host your image. Don't have one? You can create one quickly following one of these 15 | guides: 16 | - [Docker Hub Repository Documentaion](https://docs.docker.com/docker-hub/repos/) 17 | - [Azure Container Registry Documentation](https://docs.microsoft.com/en-us/azure/container-registry/) 18 | - You have access to an Azure subscription. ([Don't have one? Get started for free, right now.](https://aka.ms/buffalo-free-account)) 19 | 20 | ### Step-By-Step 21 | 22 | 1. Navigate to the root directory of your project: 23 | 24 | ``` bash 25 | cd {your project root} 26 | ``` 27 | 28 | 2. Build a Docker image from your project's defintion file: 29 | 30 | ``` bash 31 | docker build -t [{repository}/]{application}[:tag] . 32 | ``` 33 | 34 | i.e. 35 | 36 | ``` bash 37 | docker build -t marstr.azurecr.io/musicvotes:latest . 38 | ``` 39 | 40 | 3. Authenticate with Docker so that you can push the image to your repository: 41 | 42 | To protect your password from being logged, put it in a file named `mypassword.txt` then pipe it into docker: 43 | 44 | ``` bash 45 | mypassword.txt | docker login {repository} -u {username} --password-stdin 46 | ``` 47 | 48 | i.e. 49 | 50 | ``` bash 51 | mypassword.txt | docker login marstr.azurecr.io -u marstr --password-stdin 52 | ``` 53 | 54 | 4. Push your image to the Docker repository: 55 | 56 | ``` bash 57 | docker push {repository}/{application}/{application}[:{tag}] 58 | ``` 59 | 60 | i.e. 61 | 62 | ``` bash 63 | docker push marstr.azurecr.io/musicvotes:latest 64 | ``` 65 | 66 | 5. Provision the infrastructure in Azure that will host your web application: 67 | 68 | ``` bash 69 | buffalo azure provision --subscription {subscriptionID} --image {imageID} 70 | ``` 71 | 72 | i.e. 73 | 74 | ``` bash 75 | buffalo azure provision --subscription 659641ac-3e41-4ee7-9eb1-26c84469893d --image marstr.azurecr.io/musicvotes:latest 76 | ``` 77 | 78 | Don't know your Azure subscription ID? A few easy steps in the Portal will solve that: 79 | [[blog] Getting your Azure Subscription GUID (new portal)](https://blogs.msdn.microsoft.com/mschray/2016/03/18/getting-your-azure-subscription-guid-new-portal/) 80 | 81 | You're done! After a few minutes, your site will be live on Azure! -------------------------------------------------------------------------------- /cmd/eventgrid_types.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | var wellKnownEvents = map[string]string{ 4 | "Microsoft.ContainerRegistry.ImagePushed": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ContainerRegistryImagePushedEventData", 5 | "Microsoft.ContainerRegistry.ImageDeleted": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ContainerRegistryImageDeletedEventData", 6 | "Microsoft.EventGrid.SubscriptionValidationEvent": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.SubscriptionValidationEventData", 7 | "Microsoft.EventGrid.SubscriptionDeletedEvent": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.SubscriptionDeletedEventData", 8 | "Microsoft.EventHub.CaptureFileCreated": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.EventHubCaptureFileCreatedEventData", 9 | "Microsoft.Devices.DeviceCreated": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.IotHubDeviceCreatedEventData", 10 | "Microsoft.Devices.DeviceDeleted": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.IotHubDeviceDeletedEventData", 11 | "Microsoft.Media.JobStateChanged": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.MediaJobStateChangeEventData", 12 | "Microsoft.Resources.ResourceDeleteCancel": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ResourceDeleteCancelData", 13 | "Microsoft.Resources.ResourceDeleteFailure": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ResourceDeleteFailureData", 14 | "Microsoft.Resources.ResourceDeleteSuccess": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ResourceDeleteSuccessData", 15 | "Microsoft.Resources.ResourceWriteCancel": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ResourceWriteCancelData", 16 | "Microsoft.Resources.ResourceWriteFailure": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ResourceWriteFailureData", 17 | "Microsoft.Resources.ResourceWriteSuccess": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ResourceWriteSuccessData", 18 | "Microsoft.ServiceBus.ActiveMessagesAvailableWithNoListeners": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ServiceBusActiveMessagesAvailableWithNoListenersEventData", 19 | "Microsoft.ServiceBus.DeadletterMessagesAvailableWithNoListenersEvent": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.ServiceBusDeadletterMessagesAvailableWithNoListenersEventData", 20 | "Microsoft.Storage.BlobCreated": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.StorageBlobCreatedEventData", 21 | "Microsoft.Storage.BlobDeleted": "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid.StorageBlobDeletedEventData", 22 | } 23 | -------------------------------------------------------------------------------- /sdk/eventgrid/type_dispatch_subscriber.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "net/http" 7 | "strings" 8 | "sync" 9 | 10 | "github.com/gobuffalo/buffalo" 11 | ) 12 | 13 | // TypeDispatchSubscriber offers an indirection for calling a function when 14 | // an Event Grid Event has a particular value for the property `eventType`. 15 | // While the `EventHandler` interface does not itself has 16 | type TypeDispatchSubscriber struct { 17 | Subscriber 18 | bindings map[string]EventHandler 19 | normalizeTypeCase bool 20 | } 21 | 22 | // NewTypeDispatchSubscriber initializes a new empty TypeDispathSubscriber. 23 | func NewTypeDispatchSubscriber(parent Subscriber) (created *TypeDispatchSubscriber) { 24 | created = &TypeDispatchSubscriber{ 25 | Subscriber: parent, 26 | bindings: make(map[string]EventHandler), 27 | } 28 | return 29 | } 30 | 31 | // Bind ties together an Event Type identifier string and a function that knows how to handle it. 32 | func (s *TypeDispatchSubscriber) Bind(eventType string, handler EventHandler) *TypeDispatchSubscriber { 33 | s.bindings[s.NormalizeEventType(eventType)] = handler 34 | return s 35 | } 36 | 37 | // Unbind removes the mapping between an Event Type string and the associated EventHandler, if 38 | // such a mapping exists. 39 | func (s *TypeDispatchSubscriber) Unbind(eventType string) *TypeDispatchSubscriber { 40 | delete(s.bindings, s.NormalizeEventType(eventType)) 41 | return s 42 | } 43 | 44 | // NormalizeEventType applies casing rules 45 | func (s TypeDispatchSubscriber) NormalizeEventType(eventType string) string { 46 | if s.normalizeTypeCase { 47 | eventType = strings.ToUpper(eventType) 48 | } 49 | return eventType 50 | } 51 | 52 | // Receive is a `buffalo.Handler` which inspects a request sent from an Event Grid Topic, 53 | // and triages each event in the batch by the "eventType" property in the Event metadata. 54 | // If handler for an Event's type is present, the event will be passed to that Handler. 55 | // Should no Handler be specifically bound to that Event Type string, a default Handler 56 | // is called. 57 | // When no Handler is found, even a default, an HTTP 400 Status Code is returned. 58 | // Each Event is handed to exactly one Handler. If even one of those handlers returns a 59 | // response code that is not an HTTP 200 OR 201, this handler will return an HTTP 500. 60 | func (s TypeDispatchSubscriber) Receive(c buffalo.Context) error { 61 | var events []Event 62 | 63 | if err := c.Bind(&events); err != nil { 64 | return c.Error(http.StatusBadRequest, err) 65 | } 66 | 67 | ctx := NewContext(c) 68 | var wg sync.WaitGroup 69 | for _, event := range events { 70 | wg.Add(1) 71 | go func(event Event) { 72 | if handler, ok := s.Handler(event.EventType); ok { 73 | handler(ctx, event) 74 | } else if handler, ok = s.Handler(EventTypeWildcard); ok { 75 | handler(ctx, event) 76 | } else { 77 | ctx.Error(http.StatusBadRequest, fmt.Errorf("no Handler found for type %q", event.EventType)) 78 | } 79 | wg.Done() 80 | }(event) 81 | } 82 | wg.Wait() 83 | 84 | if ctx.ResponseHasFailure() { 85 | return c.Error(http.StatusInternalServerError, errors.New("at least one handler failed to process an event in this batch")) 86 | } 87 | c.Response().WriteHeader(http.StatusOK) 88 | return nil 89 | } 90 | 91 | // Handler gets the EventHandler meant to process a particular Event Grid Event Type. 92 | func (s TypeDispatchSubscriber) Handler(eventType string) (handler EventHandler, ok bool) { 93 | if s.normalizeTypeCase { 94 | eventType = strings.ToUpper(eventType) 95 | } 96 | handler, ok = s.bindings[eventType] 97 | return 98 | } 99 | -------------------------------------------------------------------------------- /sdk/eventgrid/subscription_validation_test.go: -------------------------------------------------------------------------------- 1 | package eventgrid_test 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io" 8 | "io/ioutil" 9 | "net/http" 10 | "testing" 11 | 12 | "github.com/Azure/buffalo-azure/sdk/eventgrid" 13 | "github.com/gobuffalo/buffalo" 14 | ) 15 | 16 | func TestSubscriptionValidationMiddleware_SubscriptionEvent(t *testing.T) { 17 | setFailure := func(c buffalo.Context) error { 18 | t.Fail() 19 | return c.Error(http.StatusInternalServerError, errors.New("`SubscriptionValidationMiddleware` did not detect request for validation")) 20 | } 21 | 22 | subject := eventgrid.SubscriptionValidationMiddleware(setFailure) 23 | 24 | req, err := http.NewRequest(http.MethodPost, "localhost", bytes.NewReader([]byte(`[{ 25 | "id": "2d1781af-3a4c-4d7c-bd0c-e34b19da4e66", 26 | "topic": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", 27 | "subject": "", 28 | "data": { 29 | "validationCode": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6" 30 | }, 31 | "eventType": "Microsoft.EventGrid.SubscriptionValidationEvent", 32 | "eventTime": "2018-01-25T22:12:19.4556811Z", 33 | "metadataVersion": "1", 34 | "dataVersion": "1" 35 | }]`))) 36 | 37 | req.Header.Add("Aeg-Event-Type", "SubscriptionValidation") 38 | req.Header.Add("Content-Type", "application/json") 39 | 40 | ctx := NewMockContext(req) 41 | 42 | err = subject(ctx) 43 | if err != nil { 44 | t.Error(err) 45 | return 46 | } 47 | 48 | type SubscriptionValidationResponse struct { 49 | ResponseCode string `json:"validationResponse,omitempty"` 50 | } 51 | 52 | var seenResponse SubscriptionValidationResponse 53 | dec := json.NewDecoder(ctx.Response().(*MockResponseWriter).Body()) 54 | 55 | err = dec.Decode(&seenResponse) 56 | if err != nil { 57 | t.Error(err) 58 | return 59 | } 60 | 61 | if want := "512d38b6-c7b8-40c8-89fe-f46f9e9622b6"; seenResponse.ResponseCode != want { 62 | t.Logf("got: %s want: %s", seenResponse.ResponseCode, want) 63 | t.Fail() 64 | } 65 | } 66 | 67 | func TestSubscriptionValidationMiddleware_OtherEvent(t *testing.T) { 68 | var called bool 69 | setCalled := func(c buffalo.Context) error { 70 | called = true 71 | return nil 72 | } 73 | 74 | subject := eventgrid.SubscriptionValidationMiddleware(setCalled) 75 | 76 | req, err := http.NewRequest(http.MethodPost, "localhost", bytes.NewReader([]byte(`[{ 77 | "topic": "/subscriptions/{subscription-id}/resourceGroups/Storage/providers/Microsoft.Storage/storageAccounts/xstoretestaccount", 78 | "subject": "/blobServices/default/containers/oc2d2817345i200097container/blobs/oc2d2817345i20002296blob", 79 | "eventType": "Microsoft.Storage.BlobCreated", 80 | "eventTime": "2017-06-26T18:41:00.9584103Z", 81 | "id": "831e1650-001e-001b-66ab-eeb76e069631", 82 | "data": { 83 | "api": "PutBlockList", 84 | "clientRequestId": "6d79dbfb-0e37-4fc4-981f-442c9ca65760", 85 | "requestId": "831e1650-001e-001b-66ab-eeb76e000000", 86 | "eTag": "0x8D4BCC2E4835CD0", 87 | "contentType": "application/octet-stream", 88 | "contentLength": 524288, 89 | "blobType": "BlockBlob", 90 | "url": "https://oc2d2817345i60006.blob.core.windows.net/oc2d2817345i200097container/oc2d2817345i20002296blob", 91 | "sequencer": "00000000000004420000000000028963", 92 | "storageDiagnostics": { 93 | "batchId": "b68529f3-68cd-4744-baa4-3c0498ec19f0" 94 | } 95 | }, 96 | "dataVersion": "", 97 | "metadataVersion": "1" 98 | }]`))) 99 | 100 | req.Header.Add("Content-Type", "application/json") 101 | 102 | ctx := NewMockContext(req) 103 | 104 | err = subject(ctx) 105 | if err != nil { 106 | t.Error(err) 107 | return 108 | } 109 | 110 | respBodyLength, err := io.Copy(ioutil.Discard, ctx.Response().(*MockResponseWriter).Body()) 111 | if err != nil { 112 | t.Error(err) 113 | return 114 | } 115 | 116 | if respBodyLength != 0 { 117 | t.Logf("expected an empty body") 118 | t.Fail() 119 | } 120 | 121 | if called == false { 122 | t.Logf("handler pass through never occured") 123 | t.Fail() 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /generators/common/import_bag_test.go: -------------------------------------------------------------------------------- 1 | package common_test 2 | 3 | import ( 4 | "testing" 5 | 6 | . "github.com/Azure/buffalo-azure/generators/common" 7 | ) 8 | 9 | func TestImportBag_AddImport_Unique(t *testing.T) { 10 | testCases := [][]PackagePath{ 11 | { 12 | "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid", 13 | }, 14 | { 15 | "github.com/Azure/buffalo-azure/generators/common", 16 | "github.com/Azure/buffalo-azure/sdk/eventgrid", 17 | }, 18 | { 19 | "fmt", 20 | "github.com/Azure/buffalo-azure/generators/common", 21 | }, 22 | } 23 | 24 | for _, tc := range testCases { 25 | t.Run("", func(t *testing.T) { 26 | 27 | seen := map[PackageSpecifier]struct{}{} 28 | subject := NewImportBag() 29 | 30 | for _, pkgPath := range tc { 31 | spec := subject.AddImport(pkgPath) 32 | if _, ok := seen[spec]; ok { 33 | t.Logf("duplicate spec returned") 34 | t.Fail() 35 | } 36 | } 37 | }) 38 | } 39 | } 40 | 41 | func TestImportBag_List(t *testing.T) { 42 | testCases := []struct { 43 | inputs []PackagePath 44 | expected []string 45 | }{ 46 | { 47 | inputs: []PackagePath{ 48 | "github.com/Azure/buffalo-azure/generators/common", 49 | "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid", 50 | }, 51 | expected: []string{ 52 | `"github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`, 53 | `"github.com/Azure/buffalo-azure/generators/common"`, 54 | }, 55 | }, 56 | { 57 | inputs: []PackagePath{ 58 | "github.com/Azure/buffalo-azure/sdk/eventgrid", 59 | "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid", 60 | }, 61 | expected: []string{ 62 | `eventgrid1 "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`, 63 | `"github.com/Azure/buffalo-azure/sdk/eventgrid"`, 64 | }, 65 | }, 66 | { 67 | inputs: []PackagePath{ 68 | "github.com/Azure/buffalo-azure/sdk/eventgrid", 69 | "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid", 70 | "github.com/Azure/buffalo-azure/generators/eventgrid", 71 | }, 72 | expected: []string{ 73 | `eventgrid1 "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`, 74 | `eventgrid2 "github.com/Azure/buffalo-azure/generators/eventgrid"`, 75 | `"github.com/Azure/buffalo-azure/sdk/eventgrid"`, 76 | }, 77 | }, 78 | { 79 | inputs: []PackagePath{ 80 | "github.com/Azure/buffalo-azure/sdk/eventgrid", 81 | "github.com/Azure/buffalo-azure/sdk/eventgrid", 82 | }, 83 | expected: []string{ 84 | `"github.com/Azure/buffalo-azure/sdk/eventgrid"`, 85 | }, 86 | }, 87 | } 88 | 89 | for _, tc := range testCases { 90 | t.Run("", func(t *testing.T) { 91 | subject := NewImportBag() 92 | for _, i := range tc.inputs { 93 | subject.AddImport(i) 94 | } 95 | 96 | got := subject.List() 97 | 98 | if len(got) != len(tc.expected) { 99 | t.Logf("got: %d imports want: %d imports", len(got), len(tc.expected)) 100 | t.Fail() 101 | } 102 | 103 | for i, val := range tc.expected { 104 | if got[i] != val { 105 | t.Logf("at %d\n\tgot: %s\n\twant: %s", i, got[i], val) 106 | t.Fail() 107 | } 108 | } 109 | }) 110 | } 111 | } 112 | 113 | func TestImportBag_NewImportBagFromFile(t *testing.T) { 114 | testCases := []struct { 115 | inputFile string 116 | expected []string 117 | }{ 118 | {"./testdata/main.go", []string{`"fmt"`, `_ "github.com/Azure/azure-sdk-for-go/services/eventgrid/2018-01-01/eventgrid"`}}, 119 | } 120 | 121 | for _, tc := range testCases { 122 | t.Run("", func(t *testing.T) { 123 | subject, err := NewImportBagFromFile(tc.inputFile) 124 | if err != nil { 125 | t.Error(err) 126 | return 127 | } 128 | 129 | got := subject.List() 130 | 131 | for i, imp := range tc.expected { 132 | if imp != got[i] { 133 | t.Logf("at: %d\n\tgot: %s\n\twant: %s", i, got[i], imp) 134 | t.Fail() 135 | } 136 | } 137 | 138 | if len(got) != len(tc.expected) { 139 | t.Logf("got: %d imports want: %d imports", len(got), len(tc.expected)) 140 | t.Fail() 141 | } 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /cmd/testdata/template1.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://schema.management.azure.com/schemas/2014-04-01-preview/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "imageName" : { 6 | "type": "String", 7 | "defaultValue": "appsvc/sample-hello-world:latest" 8 | }, 9 | "name": { 10 | "type": "String", 11 | "defaultValue": "[concat('site', uniqueString(resourceGroup().id, deployment().name))]" 12 | }, 13 | "database": { 14 | "type": "String", 15 | "defaultValue": "none", 16 | "allowedValues": [ 17 | "none", 18 | "postgres" 19 | ] 20 | }, 21 | "databaseName": { 22 | "type": "String", 23 | "defaultValue": "buffalo_development" 24 | }, 25 | "databaseAdministratorLogin": { 26 | "type": "String", 27 | "defaultValue": "[concat('admin', parameters('name'))]" 28 | }, 29 | "databaseAdministratorLoginPassword": { 30 | "type": "SecureString", 31 | "defaultValue": "" 32 | } 33 | }, 34 | "variables": { 35 | "hostingPlanName": "[concat('hostingPlan-', parameters('name'))]", 36 | "postgresName": "[concat(parameters('name'), '-postgres')]", 37 | "postgresConnection": "[concat('postgres://', parameters('databaseAdministratorLogin'), ':', parameters('databaseAdministratorLoginPassword'), '@', variables('postgresname'), '.postgres.database.azure.com/', parameters('databaseName'), '?sslmode=required')]" 38 | }, 39 | "resources": [ 40 | { 41 | "type": "Microsoft.Web/sites", 42 | "name": "[parameters('name')]", 43 | "apiVersion": "2016-03-01", 44 | "location": "[resourceGroup().location]", 45 | "tags": { 46 | "[concat('hidden-related:', subscription().id, '/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]": "empty", 47 | "gobuffalo": "empty" 48 | }, 49 | "properties": { 50 | "name": "[parameters('name')]", 51 | "siteConfig": { 52 | "appSettings": [ 53 | { 54 | "name": "WEBSITES_ENABLE_APP_SERVICE_STORAGE", 55 | "value": "false" 56 | } 57 | ], 58 | "connectionStrings":[ 59 | { 60 | "name":"DATABASE_URL", 61 | "connectionString": "[if(equals(parameters('database'), 'postgres'), variables('postgresConnection'), 'not applicable')]", 62 | "type":"custom" 63 | } 64 | ], 65 | "appCommandLine": "", 66 | "linuxFxVersion": "[concat('DOCKER|', parameters('imageName'))]" 67 | }, 68 | "serverFarmId": "[concat(subscription().id, '/resourcegroups/', resourceGroup().name, '/providers/Microsoft.Web/serverfarms/', variables('hostingPlanName'))]", 69 | "hostingEnvironment": "" 70 | }, 71 | "dependsOn": [ 72 | "[variables('hostingPlanName')]", 73 | "[variables('postgresName')]" 74 | ] 75 | }, 76 | { 77 | "type": "Microsoft.Web/serverfarms", 78 | "sku": { 79 | "Tier": "Basic", 80 | "Name": "B1" 81 | }, 82 | "kind": "linux", 83 | "name": "[variables('hostingPlanName')]", 84 | "apiVersion": "2016-09-01", 85 | "location": "[resourceGroup().location]", 86 | "properties": { 87 | "name": "[variables('hostingPlanName')]", 88 | "workerSizeId": "0", 89 | "reserved": true, 90 | "numberOfWorkers": "1", 91 | "hostingEnvironment": "" 92 | } 93 | }, 94 | { 95 | "condition":"[equals(parameters('database'), 'postgres')]", 96 | "type": "Microsoft.DBforPostgreSQL/servers", 97 | "sku": { 98 | "name": "B_Gen5_1", 99 | "family": "Gen5", 100 | "capacity": "", 101 | "size": "5120", 102 | "tier":"Basic" 103 | }, 104 | "kind":"", 105 | "name":"[variables('postgresName')]", 106 | "apiVersion": "2017-12-01-preview", 107 | "location":"[resourceGroup().location]", 108 | "properties": { 109 | "version": "9.6", 110 | "administratorLogin": "[parameters('databaseAdministratorLogin')]", 111 | "administratorLoginPassword": "[parameters('databaseAdministratorLoginPassword')]" 112 | } 113 | } 114 | ] 115 | } -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors. 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "fmt" 25 | "os" 26 | 27 | homedir "github.com/mitchellh/go-homedir" 28 | "github.com/spf13/cobra" 29 | "github.com/spf13/viper" 30 | ) 31 | 32 | type logOutputLevel string 33 | 34 | const ( 35 | logOutputLevelDebug = "debug" 36 | logOutputLevelInfo = "info" 37 | logOutputLevelWarn = "warn" 38 | logOutputLevelError = "error" 39 | logOutputLevelFatal = "fatal" 40 | logOutputLevelPanic = "panic" 41 | 42 | logOutputLevelName = "output-level" 43 | logOutputLevelShorthand = "o" 44 | logOutputLevelDefault = logOutputLevelInfo 45 | logOutputLevelUsage = "The amount of output you'd like to see. Options include: " + logOutputLevelDebug + ", " + logOutputLevelInfo + ", " + logOutputLevelWarn + ", " + logOutputLevelError + ", " + logOutputLevelFatal + ", and " + logOutputLevelPanic 46 | ) 47 | 48 | var cfgFile string 49 | 50 | var rootConfig = viper.New() 51 | 52 | // rootCmd represents the base command when called without any subcommands 53 | var rootCmd = &cobra.Command{ 54 | Use: "buffalo azure", 55 | Short: "Tools for integrating your Buffalo application with Azure.", 56 | // Uncomment the following line if your bare application 57 | // has an action associated with it: 58 | // Run: func(cmd *cobra.Command, args []string) { }, 59 | } 60 | 61 | // Execute adds all child commands to the root command and sets flags appropriately. 62 | // This is called by main.main(). It only needs to happen once to the rootCmd. 63 | func Execute() { 64 | if err := rootCmd.Execute(); err != nil { 65 | fmt.Println(err) 66 | os.Exit(1) 67 | } 68 | } 69 | 70 | func init() { 71 | // Commented out until config-file story can be polished. This effort is tracked by Azure/buffalo-azure#40 72 | //cobra.OnInitialize(initConfig) 73 | 74 | // Here you will define your flags and configuration settings. 75 | // Cobra supports persistent flags, which, if defined here, 76 | // will be global for your application. 77 | // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.buffalo-azure.yaml)") 78 | 79 | // Cobra also supports local flags, which will only run 80 | // when this action is called directly. 81 | // rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 82 | 83 | rootCmd.PersistentFlags().StringP(logOutputLevelName, logOutputLevelShorthand, logOutputLevelDefault, logOutputLevelUsage) 84 | rootCmd.PersistentFlags().BoolP(VerboseName, VerboseShortname, false, verboseUsage) 85 | 86 | rootConfig.BindPFlags(rootCmd.PersistentFlags()) 87 | } 88 | 89 | // initConfig reads in config file and ENV variables if set. 90 | func initConfig() { 91 | if cfgFile != "" { 92 | // Use config file from the flag. 93 | viper.SetConfigFile(cfgFile) 94 | } else { 95 | // Find home directory. 96 | home, err := homedir.Dir() 97 | if err != nil { 98 | fmt.Println(err) 99 | os.Exit(1) 100 | } 101 | 102 | // Search config in home directory with name ".buffalo-azure" (without extension). 103 | viper.AddConfigPath(home) 104 | viper.SetConfigName(".buffalo-azure") 105 | } 106 | 107 | viper.AutomaticEnv() // read in environment variables that match 108 | 109 | // If a config file is found, read it in. 110 | if err := viper.ReadInConfig(); err == nil { 111 | fmt.Println("Using config file:", viper.ConfigFileUsed()) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /cmd/eventgrid.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "errors" 25 | "fmt" 26 | "os" 27 | "reflect" 28 | "strings" 29 | 30 | "github.com/gobuffalo/buffalo/meta" 31 | "github.com/spf13/cobra" 32 | 33 | "github.com/Azure/buffalo-azure/generators/eventgrid" 34 | ) 35 | 36 | // eventgridCmd represents the eventgrid command 37 | var eventgridCmd = &cobra.Command{ 38 | Use: "eventgrid [:...]", 39 | Aliases: []string{"eg"}, 40 | Short: "Generates new action(s) for handling Azure Event Grid events.", 41 | Long: `Add actions for reacting to Event Grid Events to your Buffalo application. 42 | 43 | An EventTypeString is arbitrary, but often resembles a namespace. These are not 44 | Go-specific and are decided by the tool originating the Event. The ones provided 45 | by Microsoft take the form "Microsoft..". Some examples include: 46 | - Microsoft.EventGrid.SubscriptionValidationEvent 47 | - Microsoft.Resources.ResourceWriteSuccess 48 | - Microsoft.Storage.BlobCreated 49 | 50 | A type identifier is a Go-specific detail of this Plugin. They take the form 51 | ".". Some examples include: 52 | - github.com/Azure/buffalo-azure/sdk/eventgrid.Cache 53 | - github.com/markbates/cash.Money 54 | For the purpose of the Event Grid Buffalo generator, it will make most sense 55 | to specify a type which well accomodates the unmarshaling a JSON object into 56 | your type. 57 | 58 | All together, you may find yourself running a command like: 59 | 60 | buffalo generate eventgrid blobs \ 61 | Microsoft.Storage.BlobCreated \ 62 | Microsoft.Storage.BlobDeleted 63 | 64 | or 65 | 66 | buffalo generate eventgrid github \ 67 | GitHub.PullRequest:github.com/google/go-github/github.PullRequestEvent \ 68 | GitHub.Label:github.com/google/go-github/github.LabelEvent 69 | 70 | More documentation about Event Grid can be found at: 71 | https://azure.microsoft.com/en-us/services/event-grid/`, 72 | Run: func(cmd *cobra.Command, args []string) { 73 | 74 | name := args[0] 75 | types := make(map[string]reflect.Type, len(args[1:])) 76 | 77 | for _, arg := range args[1:] { 78 | eventType, goType, err := parseEventArg(arg) 79 | if err != nil { 80 | return 81 | } 82 | 83 | types[eventType], err = eventgrid.NewTypeStubIdentifier(goType) 84 | } 85 | 86 | gen := eventgrid.Generator{} 87 | 88 | if err := gen.Run(meta.New("."), name, types); err != nil { 89 | fmt.Fprintln(os.Stderr, "unable to create subscriber file: ", err) 90 | os.Exit(1) 91 | } 92 | }, 93 | Args: func(cmd *cobra.Command, args []string) error { 94 | if len(args) < 1 { 95 | return errors.New("missing required arguments") 96 | } 97 | 98 | for _, arg := range args[1:] { 99 | if _, _, err := parseEventArg(arg); err != nil { 100 | return err 101 | } 102 | } 103 | 104 | return nil 105 | }, 106 | } 107 | 108 | func init() { 109 | rootCmd.AddCommand(eventgridCmd) 110 | 111 | // Here you will define your flags and configuration settings. 112 | 113 | // Cobra supports Persistent Flags which will work for this command 114 | // and all subcommands, e.g.: 115 | // eventgridCmd.PersistentFlags().String("foo", "", "A help for foo") 116 | 117 | // Cobra supports local flags which will only run when this command 118 | // is called directly, e.g.: 119 | // eventgridCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 120 | } 121 | 122 | func parseEventArg(arg string) (string, string, error) { 123 | if typeIdentifier, ok := wellKnownEvents[arg]; ok { 124 | return arg, typeIdentifier, nil 125 | } 126 | 127 | last := strings.LastIndex(arg, ":") 128 | if last < 0 { 129 | return "", "", errors.New("unexpected argument format") 130 | } 131 | return arg[:last], arg[last+1:], nil 132 | } 133 | -------------------------------------------------------------------------------- /generators/eventgrid/builder/builder.go: -------------------------------------------------------------------------------- 1 | // Stuff 2 | package main 3 | 4 | import ( 5 | "bytes" 6 | "context" 7 | "errors" 8 | "fmt" 9 | "io" 10 | "io/ioutil" 11 | "os" 12 | "path/filepath" 13 | "strings" 14 | ) 15 | 16 | type file struct { 17 | path string 18 | contents []byte 19 | } 20 | 21 | func main() { 22 | exitStatus := 1 23 | defer func() { 24 | os.Exit(exitStatus) 25 | }() 26 | var err error 27 | 28 | var output io.Writer = os.Stdout 29 | for i, arg := range os.Args { 30 | if arg == "-o" { 31 | output, err = os.Create(os.Args[i+1]) 32 | os.Args = append(os.Args[:i], os.Args[i+2]) 33 | if err != nil { 34 | return 35 | } 36 | break 37 | } 38 | } 39 | 40 | ctx, cancel := context.WithCancel(context.Background()) 41 | defer cancel() 42 | 43 | discoveredFiles := make(chan file) 44 | 45 | stagingHandle, err := ioutil.TempFile("", "buffalo_staging") 46 | if err != nil { 47 | fmt.Fprintln(os.Stderr, "Unable to create a temporary file to stage the results.") 48 | return 49 | } 50 | defer os.Remove(stagingHandle.Name()) 51 | 52 | fmt.Fprintln(stagingHandle, `// generated by github.com/Azure/buffalo-azure/generators/eventgrid/builder 53 | // DO NOT EDIT - instead run "go generate ./..." 54 | 55 | package eventgrid 56 | 57 | var staticTemplates = make(TemplateCache) 58 | 59 | func init() {`) 60 | 61 | writeErr := make(chan error) 62 | go func(result chan<- error) { 63 | result <- writeFileEntry(ctx, stagingHandle, discoveredFiles) 64 | close(result) 65 | }(writeErr) 66 | 67 | readErr := make(chan error) 68 | go func(result chan<- error) { 69 | for _, arg := range os.Args { 70 | if err := readFiles(ctx, arg, discoveredFiles); err != nil { 71 | result <- err 72 | break 73 | } 74 | } 75 | close(result) 76 | close(discoveredFiles) 77 | }(readErr) 78 | 79 | var readDone, writeDone bool 80 | for { 81 | if readDone && writeDone { 82 | break 83 | } 84 | //time.Sleep(200 * time.Millisecond) 85 | 86 | select { 87 | case err, ok := <-readErr: 88 | if ok && err != context.Canceled && err != nil { 89 | fmt.Fprintln(os.Stderr, "unable to read files: ", err) 90 | cancel() 91 | } 92 | readDone = true 93 | case err, ok := <-writeErr: 94 | if ok && err != context.Canceled && err != nil { 95 | fmt.Fprintln(os.Stderr, "unable to write files: ", err) 96 | cancel() 97 | } 98 | writeDone = true 99 | } 100 | } 101 | 102 | fmt.Fprint(stagingHandle, "}\n") 103 | 104 | stagingHandle.Close() 105 | 106 | stagingReader, err := os.Open(stagingHandle.Name()) 107 | if err != nil { 108 | fmt.Fprintln(os.Stderr, "unable to read staging file: ", err) 109 | return 110 | } 111 | 112 | _, err = io.Copy(output, stagingReader) 113 | if err != nil { 114 | fmt.Fprintln(os.Stderr, "unable to copy staging file to Stdout: ", err) 115 | return 116 | } 117 | 118 | exitStatus = 0 119 | } 120 | 121 | func writeFileEntry(ctx context.Context, output io.Writer, input <-chan file) error { 122 | lineItem := bytes.NewBuffer([]byte{}) 123 | for { 124 | select { 125 | case <-ctx.Done(): 126 | return ctx.Err() 127 | case entry, ok := <-input: 128 | if !ok { 129 | return nil 130 | } 131 | 132 | if len(entry.contents) == 0 { 133 | return errors.New("cannot process an empty file") 134 | } 135 | 136 | fmt.Fprintf(lineItem, "\tstaticTemplates[%q] = []byte{", strings.Replace(entry.path, `\`, "/", -1)) 137 | 138 | const terminator = ", " 139 | for _, item := range entry.contents { 140 | fmt.Fprintf(lineItem, "%d%s", item, terminator) 141 | } 142 | lineItem.Truncate(lineItem.Len() - len(terminator)) 143 | 144 | fmt.Fprintln(lineItem, "}") 145 | 146 | if _, err := io.Copy(output, lineItem); err != nil { 147 | return err 148 | } 149 | lineItem.Reset() 150 | } 151 | } 152 | } 153 | 154 | var acceptedFileExtensions = []string{".tmpl", ".go"} 155 | 156 | func readFiles(ctx context.Context, root string, output chan<- file) error { 157 | addFile := func(path string, info os.FileInfo, outerError error) error { 158 | if outerError != nil { 159 | return outerError 160 | } 161 | 162 | if info.IsDir() { 163 | return nil 164 | } 165 | 166 | found := false 167 | 168 | for _, ext := range acceptedFileExtensions { 169 | if strings.EqualFold(filepath.Ext(info.Name()), ext) { 170 | found = true 171 | break 172 | } 173 | } 174 | 175 | if !found { 176 | return nil 177 | } 178 | 179 | handle, err := os.Open(path) 180 | if err != nil { 181 | return err 182 | } 183 | defer handle.Close() 184 | 185 | result := file{ 186 | path: path, 187 | } 188 | result.contents, err = ioutil.ReadAll(handle) 189 | if err != nil { 190 | return err 191 | } 192 | 193 | select { 194 | case <-ctx.Done(): 195 | return ctx.Err() 196 | case output <- result: 197 | // Intentionally Left Blank 198 | } 199 | 200 | return nil 201 | } 202 | 203 | return filepath.Walk(root, addFile) 204 | } 205 | -------------------------------------------------------------------------------- /sdk/eventgrid/context.go: -------------------------------------------------------------------------------- 1 | package eventgrid 2 | 3 | import ( 4 | "net/http" 5 | "sync" 6 | 7 | "github.com/gobuffalo/buffalo" 8 | "github.com/gobuffalo/buffalo/render" 9 | "github.com/pkg/errors" 10 | ) 11 | 12 | // SuccessStatusCodes returns an unordered list of HTTP Status Codes 13 | // that should be considered having been handled correctly. Event Grid 14 | // Topics will retry on any HTTP Status Code that is not in this list. 15 | func SuccessStatusCodes() map[int]struct{} { 16 | return successStatusCodes 17 | } 18 | 19 | var successStatusCodes = map[int]struct{}{ 20 | http.StatusOK: struct{}{}, 21 | http.StatusCreated: struct{}{}, 22 | } 23 | 24 | // Context extends `buffalo.Context` to ease communication between a Request Handler 25 | // and an Event Grid Topic. 26 | type Context struct { 27 | buffalo.Context 28 | resp *ResponseWriter 29 | data map[string]interface{} 30 | flash buffalo.Flash 31 | } 32 | 33 | // NewContext initializes a new `eventgrid.Context`. 34 | func NewContext(parent buffalo.Context) (created *Context) { 35 | created = &Context{ 36 | Context: parent, 37 | resp: NewResponseWriter(), 38 | data: make(map[string]interface{}, len(parent.Data())), 39 | } 40 | 41 | for k, v := range parent.Data() { 42 | created.data[k] = v 43 | } 44 | 45 | return 46 | } 47 | 48 | // Response fulfills Buffalo's requirement to allow folks to write a response, 49 | // but it actually just throws away anything you write to it. 50 | func (c *Context) Response() http.ResponseWriter { 51 | return c.resp 52 | } 53 | 54 | // ResponseHasFailure indicates whether or not any Status Codes not indicating 55 | // success to an Event Grid Topic were published to this Context's `ResponseWriter`. 56 | func (c *Context) ResponseHasFailure() bool { 57 | return c.resp.HasFailure() 58 | } 59 | 60 | func (c *Context) Error(status int, err error) error { 61 | c.resp.WriteHeader(status) 62 | if logger := c.Logger(); logger != nil { 63 | logger.Error(err) 64 | } 65 | return errors.WithStack(err) 66 | } 67 | 68 | // Render discards the body that is populated by the renderer, but takes the status 69 | // into consideration for how to communicate success or failue to an Event Grid Topic. 70 | func (c *Context) Render(status int, r render.Renderer) error { 71 | if logger := c.Logger(); logger != nil { 72 | c.Logger().Info("Event processed with Status Code: %d ", status) 73 | } 74 | c.resp.WriteHeader(status) 75 | return r.Render(c.Response(), c.Data()) 76 | } 77 | 78 | // Flash fetches an unused instance of Flash. 79 | func (c *Context) Flash() *buffalo.Flash { 80 | return &c.flash 81 | } 82 | 83 | // Redirect informs the Event Grid Topic that an Event was unable to be handled. 84 | func (c *Context) Redirect(status int, url string, args ...interface{}) error { 85 | c.resp.WriteHeader(status) 86 | return nil 87 | } 88 | 89 | // ResponseWriter looks like an `http.ResponseWriter`, but 90 | type ResponseWriter struct { 91 | sync.RWMutex 92 | failureSeen bool 93 | header http.Header 94 | } 95 | 96 | // NewResponseWriter initializes a ResponseWriter which will merge the responses of 97 | // several Event Grid Handlers. 98 | func NewResponseWriter() *ResponseWriter { 99 | return &ResponseWriter{ 100 | failureSeen: false, 101 | header: make(http.Header), 102 | } 103 | } 104 | 105 | // Header gets the Headers associated with this Response writer. 106 | func (w *ResponseWriter) Header() http.Header { 107 | w.RLock() 108 | defer w.RUnlock() 109 | 110 | return w.header 111 | } 112 | 113 | // Write takes a message to write to a Response, and does nothing with it. 114 | func (w *ResponseWriter) Write(x []byte) (int, error) { 115 | // Because the body of the response is thrown away, we don't need to 116 | // put a gaurd around it. 117 | return len(x), nil 118 | } 119 | 120 | // HasFailure evaluates whether or not any Status Headers have been written 121 | // to this Context that are not in the result of calling `SuccessStatusCodes`. 122 | func (w *ResponseWriter) HasFailure() bool { 123 | w.RLock() 124 | defer w.RUnlock() 125 | 126 | return w.failureSeen 127 | } 128 | 129 | // SetFailure indicates that a Status Code outside of ones an Event Grid Topic 130 | // accepts as meaning not to retry was present in one of Handlers writing to this 131 | // ResponseWriter. 132 | func (w *ResponseWriter) SetFailure() { 133 | w.Lock() 134 | defer w.Unlock() 135 | 136 | w.failureSeen = true 137 | } 138 | 139 | // WriteHeader takes an HTTP Status Code and informs the `Context` as to whether or 140 | // not there was an error processing it. 141 | func (w *ResponseWriter) WriteHeader(s int) { 142 | // Because there is only a "set" operation, there is no need to 143 | // combat the defacto race-condition present here. 144 | // Because both `HasFailure` and `SetFailure` are protected, 145 | // this function also will not trigger the `go` tool's race condition 146 | // detector. 147 | if _, ok := SuccessStatusCodes()[s]; !w.HasFailure() && !ok { 148 | w.SetFailure() 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /cmd/provision_test.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "os" 10 | "strings" 11 | "sync" 12 | "testing" 13 | "time" 14 | 15 | "github.com/Azure/go-autorest/autorest/azure" 16 | ) 17 | 18 | func init() { 19 | var err error 20 | environment, err = azure.EnvironmentFromName(provisionConfig.GetString(EnvironmentName)) 21 | if err != nil { 22 | environment = azure.PublicCloud 23 | } 24 | 25 | log.Out = ioutil.Discard 26 | } 27 | 28 | func Test_getAuthorizer(t *testing.T) { 29 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 30 | defer cancel() 31 | 32 | subscriptionID := provisionConfig.GetString(SubscriptionName) 33 | clientID := provisionConfig.GetString(ClientSecretName) 34 | clientSecret := provisionConfig.GetString(ClientSecretName) 35 | tenantID := provisionConfig.GetString(TenantIDName) 36 | 37 | if tenantID == "" || subscriptionID == "" { 38 | // If you don't want to tinker with the environment, you can pass these in as command-line arguments 39 | // to the `go test` command, the same way you would have to call the azure provision command. 40 | t.Skip("test environment not configured with a tenant or subscription") 41 | return 42 | } 43 | 44 | var wg sync.WaitGroup 45 | wg.Add(2) 46 | 47 | go t.Run("service principal, no tenant inference", func(t *testing.T) { 48 | defer wg.Done() 49 | if clientID == "" || clientSecret == "" { 50 | t.Skip("test environment not configured with a service principal") 51 | return 52 | } 53 | 54 | if auth, err := getAuthorizer(ctx, subscriptionID, clientID, clientSecret, tenantID); err != nil { 55 | t.Error(err) 56 | } else if auth == nil { 57 | t.Log("auth unexpected nil in non error case") 58 | t.Fail() 59 | } 60 | }) 61 | 62 | go t.Run("service principal, tenant inference", func(t *testing.T) { 63 | defer wg.Done() 64 | 65 | if clientID == "" || clientSecret == "" { 66 | t.Skip("test environment not configured with a service principal") 67 | return 68 | } 69 | 70 | if _, err := getAuthorizer(ctx, subscriptionID, clientID, clientSecret, ""); err == nil { 71 | // Is this failing because you've found a work around and implemented Service Principal tenant inference? 72 | // Awesome, change this test. 73 | // Otherwise, something is wrong that could cause us to mislead customers into thinking they can do tenant 74 | // inference. 75 | t.Log("tenant inference should fail when using a service principal") 76 | t.Fail() 77 | } 78 | }) 79 | 80 | wg.Wait() 81 | } 82 | 83 | func Test_getDeploymentTemplate_links(t *testing.T) { 84 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 85 | defer cancel() 86 | 87 | testCases := []string{ 88 | "https://aka.ms/buffalo-template", 89 | "http://aka.ms/buffalo-template", 90 | } 91 | 92 | for _, tc := range testCases { 93 | t.Run("", func(t *testing.T) { 94 | result, err := getDeploymentTemplate(ctx, tc) 95 | if err != nil { 96 | t.Error(err) 97 | } 98 | 99 | if result.Template == nil { 100 | t.Log("unexpected nil present in template") 101 | t.Fail() 102 | } 103 | 104 | if result.TemplateLink != nil { 105 | t.Log("unexpected value template link") 106 | t.Fail() 107 | return 108 | } 109 | }) 110 | } 111 | } 112 | 113 | func Test_getDeploymentTemplate_localFiles(t *testing.T) { 114 | ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 115 | defer cancel() 116 | 117 | testCases := []string{ 118 | "./testdata/template1.json", 119 | } 120 | 121 | for _, tc := range testCases { 122 | t.Run(tc, func(t *testing.T) { 123 | handle, err := os.Open(tc) 124 | if err != nil { 125 | t.Error(err) 126 | return 127 | } 128 | 129 | result, err := getDeploymentTemplate(ctx, tc) 130 | if err != nil { 131 | t.Error(err) 132 | return 133 | } 134 | 135 | if result.TemplateLink != nil { 136 | t.Log("unexpected value present in template link") 137 | t.Fail() 138 | } 139 | 140 | if result.Template == nil { 141 | t.Log("unexpected nil template") 142 | t.Fail() 143 | return 144 | } 145 | 146 | want, err := ioutil.ReadAll(handle) 147 | if err != nil { 148 | t.Error(err) 149 | } 150 | minimized := bytes.NewBuffer([]byte{}) 151 | enc := json.NewEncoder(minimized) 152 | err = enc.Encode(json.RawMessage(want)) 153 | if err != nil { 154 | t.Error(err) 155 | return 156 | } 157 | want = minimized.Bytes() 158 | want = []byte(strings.TrimSpace(string(want))) 159 | 160 | got, err := json.Marshal(result.Template) 161 | 162 | report := func(got, want []byte) string { 163 | shrink := func(target []byte, maxLength int) (retval []byte) { 164 | if len(target) > maxLength { 165 | retval = append(target[:maxLength/2], []byte("...")...) 166 | retval = append(retval, target[len(target)-maxLength/2:]...) 167 | } else { 168 | retval = target 169 | } 170 | return 171 | } 172 | 173 | const maxLength = 30 174 | 175 | gotLength := len(got) 176 | got = shrink(got, maxLength) 177 | 178 | wantLength := len(want) 179 | want = shrink(want, maxLength) 180 | 181 | return fmt.Sprintf("\ngot (len %d):\n\t%q\nwant (len %d):\n\t%q", gotLength, got, wantLength, want) 182 | } 183 | 184 | if len(want) == len(got) { 185 | for i, current := range want { 186 | if got[i] != current { 187 | t.Log(report(got, want)) 188 | t.Fail() 189 | break 190 | } 191 | } 192 | } else { 193 | t.Log(report(got, want)) 194 | t.Fail() 195 | } 196 | }) 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | 4 |

5 | 6 | # Buffalo-Azure 7 | 8 | [![GoDoc](https://godoc.org/github.com/Azure/buffalo-azure?status.svg)](https://godoc.org/github.com/Azure/buffalo-azure) 9 | [![Build Status](https://travis-ci.org/Azure/buffalo-azure.svg?branch=master)](https://travis-ci.org/Azure/buffalo-azure) 10 | 11 | By extending [gobuffalo](https://gobuffalo.io), we seek to empower rapid web developers to deploy to Azure easily, and 12 | even generate code that will integrate well with Azure services. 13 | 14 | Don't have an Azure account? [Get started right now for free.](https://aka.ms/buffalo-free-account) 15 | 16 | #### provision 17 | 18 | `buffalo azure provision [flags]` 19 | 20 | It's fun to tinker with a website locally, but at some point you've got to get it hosted. Use the provision command to 21 | create the necessary infrastructure and services to run your website on Microsoft's Cloud. All you need is to use the 22 | Dockerfile already generated by Buffalo to create an image that will be run in an 23 | [Azure Web App for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/). 24 | 25 | Read a comprehensive walk-through of using Docker and the `buffalo azure provision` command to have your Buffalo 26 | application running on Azure in no-time. 27 | - [Local Docker Build](./documentation/deployment/Deployment.LocalDockerBuild.md) 28 | - [Continuous Deployment Using GitHub and Docker Hub](./documentation/deployment/Deployment.DockerHubCloudBuild.md) 29 | 30 | #### eventgrid 31 | 32 | `buffalo generate eventgrid {name} [flags]` 33 | 34 | [Azure Event Grid](https://docs.microsoft.com/en-us/azure/event-grid/overview) is an HTTP based messaging solution, that 35 | seeks to empower you while writing event-driven reactive web applications. Many Azure Services are already wired up to 36 | start informing you when their events happen using Event Grid. 37 | 38 | Running this command will add an action to your buffalo application that can be registered with an Event Grid Topic. It 39 | automatically responds to Subscription Validation events, and dispatches to different methods based on the Event Type 40 | string in an Event definition. 41 | 42 | ### Installation 43 | 44 | This is an extension, so before you install Buffalo-Azure, make sure you've already 45 | [installed Buffalo](https://gobuffalo.io/en/docs/installation). 46 | 47 | #### Build from Source 48 | 49 | ##### go get 50 | 51 | Use `go get` to acquire and install from source. Versions of the project after `v0.2.0` use 52 | Go modules exclusively, which means you'll need Go 1.11 or later. 53 | 54 | For more information on modules, see the [Go modules wiki](https://github.com/golang/go/wiki/Modules). 55 | 56 | ``` bash 57 | go get -u github.com/Azure/buffalo-azure 58 | ``` 59 | 60 | #### Binary Distribution 61 | 62 | This isn't supported yet, but if you'd like to see it, go get it moved up our backlog by thumbs-upping or commenting on 63 | [Azure/buffalo-azure#37](https://github.com/Azure/buffalo-azure/issues/37). 64 | 65 | ### Authentication 66 | 67 | While working with Buffalo-Azure, there are two options for establishing an identity that should be used for any 68 | operation requiring Azure authentication: 69 | 70 | #### Device Authentication 71 | 72 | This option requires manual attention, but is simple. Just add `--use-device-auth` to the command line and you'll be 73 | prompted to login with your identity. If you omit data that can be inferred, it may be slow, but Buffalo-Azure will just 74 | figure it out for you. 75 | 76 | #### Service Principals 77 | 78 | Using the command-line arguments `--client-id`, `--client-secret`, and `--tenant-id`, or the corresponding environment 79 | variables `AZURE_CLIENT_ID`, `AZURE_CLIENT_SECRET`, `AZURE_TENANT_ID`, one can specify a Service Principal's identity. 80 | 81 | To learn more about getting started with Service Principals you can look here: 82 | - Service Principal Overview: https://docs.microsoft.com/en-us/azure/active-directory/develop/active-directory-application-objects 83 | - Using the Azure CLI 2.0 to create a Service Principal: [https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?toc=%2Fazure%2Fazure-resource-manager%2Ftoc.json&view=azure-cli-latest) 84 | - Using the Azure Portal: [https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal?view=azure-cli-latest) 85 | - Using Azure PowerShell: [https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal?view=azure-cli-latest) 86 | 87 | ## Disclaimer 88 | This is an experiment by the Azure Developer Experience team to expand our usefulness to Go developers beyond generating 89 | SDKs. **This is not officially supported** by the Azure DevEx team, Azure, or Microsoft. 90 | 91 | 92 | ## Contributing 93 | 94 | This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License 95 | Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For 96 | details, visit https://cla.microsoft.com. 97 | 98 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide a CLA and decorate 99 | the PR appropriately (e.g., label, comment). Simply follow the instructions provided by the bot. You will only need to 100 | do this once across all repos using our CLA. 101 | 102 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 103 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact 104 | [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 105 | 106 | ## Attribution 107 | The image of Azure Gophers was drawn by [Ashley McNamara](https://medium.com/@ashleymcnamara), based on artwork by 108 | [Renee French](https://reneefrench.blogspot.com). It is licensed under the [Creative Commons 4.0 License](https://creativecommons.org/licenses/by-nc-sa/4.0/). 109 | You can find many more gophers like this one at: https://github.com/ashleymcnamara/gophers 110 | -------------------------------------------------------------------------------- /generators/common/import_bag.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "bytes" 5 | "errors" 6 | "fmt" 7 | "go/ast" 8 | "go/importer" 9 | "go/parser" 10 | "go/token" 11 | "go/types" 12 | "path" 13 | "sort" 14 | "strings" 15 | ) 16 | 17 | // PackageSpecifier is a string that represents the name that will be used to refer to 18 | // exported functions and variables from a package. 19 | type PackageSpecifier string 20 | 21 | // PackagePath is a string that refers to the location of Go package. 22 | type PackagePath string 23 | 24 | // ImportBag captures all of the imports in a Go source file, and attempts 25 | // to ease the process of working with them. 26 | type ImportBag struct { 27 | bySpec map[PackageSpecifier]PackagePath 28 | blankIdent map[PackagePath]struct{} 29 | localIdent map[PackagePath]struct{} 30 | } 31 | 32 | // NewImportBag instantiates an empty ImportBag. 33 | func NewImportBag() *ImportBag { 34 | return &ImportBag{ 35 | bySpec: make(map[PackageSpecifier]PackagePath), 36 | blankIdent: make(map[PackagePath]struct{}), 37 | localIdent: make(map[PackagePath]struct{}), 38 | } 39 | } 40 | 41 | // NewImportBagFromFile reads a Go source file, finds all imports, 42 | // and returns them as an instantiated ImportBag. 43 | func NewImportBagFromFile(filepath string) (*ImportBag, error) { 44 | f, err := parser.ParseFile(token.NewFileSet(), filepath, nil, parser.ImportsOnly) 45 | if err != nil { 46 | return nil, err 47 | } 48 | 49 | ib := NewImportBag() 50 | 51 | for _, spec := range f.Imports { 52 | pkgPath := PackagePath(strings.Trim(spec.Path.Value, `"`)) 53 | if spec.Name == nil { 54 | ib.AddImport(pkgPath) 55 | } else { 56 | ib.AddImportWithSpecifier(pkgPath, PackageSpecifier(spec.Name.Name)) 57 | } 58 | } 59 | 60 | return ib, nil 61 | } 62 | 63 | // AddImport includes a package, and returns the name that was selected to 64 | // be the specified for working with this path. It first attempts to use the 65 | // package name as the specifier. Should that cause a conflict, it determines 66 | // a unique name to be used as the specifier. 67 | // 68 | // If the path provided has already been imported, the existing name for it 69 | // is returned, but err is non-nil. 70 | func (ib *ImportBag) AddImport(pkgPath PackagePath) PackageSpecifier { 71 | spec, err := FindSpecifier(pkgPath) 72 | if err != nil { 73 | spec = PackageSpecifier(path.Base(string(pkgPath))) 74 | } 75 | specLen := len(spec) 76 | suffix := uint(1) 77 | for { 78 | err = ib.AddImportWithSpecifier(pkgPath, spec) 79 | if err == nil { 80 | break 81 | } else if err != ErrDuplicateImport { 82 | panic(err) 83 | } 84 | spec = PackageSpecifier(fmt.Sprintf("%s%d", spec[:specLen], suffix)) 85 | suffix++ 86 | } 87 | 88 | return spec 89 | } 90 | 91 | // ErrDuplicateImport is the error that will be returned when two packages are both requested 92 | // to be imported using the same specifier. 93 | var ErrDuplicateImport = errors.New("specifier already in use in ImportBag") 94 | 95 | // ErrMultipleLocalImport is the error that will be returned when the same package has been imported 96 | // to the specifer "." more than once. 97 | var ErrMultipleLocalImport = errors.New("package already imported into the local namespace") 98 | 99 | // AddImportWithSpecifier will add an import with a given name. If it would lead 100 | // to conflicting package specifiers, it returns an error. 101 | func (ib *ImportBag) AddImportWithSpecifier(pkgPath PackagePath, specifier PackageSpecifier) error { 102 | if specifier == "_" { 103 | ib.blankIdent[pkgPath] = struct{}{} 104 | return nil 105 | } 106 | 107 | if specifier == "." { 108 | if _, ok := ib.localIdent[pkgPath]; ok { 109 | return ErrMultipleLocalImport 110 | } 111 | ib.localIdent[pkgPath] = struct{}{} 112 | return nil 113 | } 114 | 115 | if impPath, ok := ib.bySpec[specifier]; ok && pkgPath != impPath { 116 | return ErrDuplicateImport 117 | } 118 | 119 | ib.bySpec[specifier] = pkgPath 120 | return nil 121 | } 122 | 123 | // FindSpecifier finds the specifier assocatied with a particular package. 124 | // 125 | // If the package was not imported, the empty string and false are returned. 126 | // 127 | // If multiple specifiers are assigned to the package, one is returned at 128 | // random. 129 | // 130 | // If the same package is imported with a named specifier, and the blank 131 | // identifier, the name is returned. 132 | func (ib ImportBag) FindSpecifier(pkgPath PackagePath) (PackageSpecifier, bool) { 133 | for k, v := range ib.bySpec { 134 | if v == pkgPath { 135 | return k, true 136 | } 137 | } 138 | 139 | if _, ok := ib.blankIdent[pkgPath]; ok { 140 | return "_", true 141 | } 142 | 143 | if _, ok := ib.localIdent[pkgPath]; ok { 144 | return ".", true 145 | } 146 | 147 | return "", false 148 | } 149 | 150 | // List returns each import statement as a slice of strings sorted alphabetically by 151 | // their import paths. 152 | func (ib *ImportBag) List() []string { 153 | specs := ib.ListAsImportSpec() 154 | retval := make([]string, len(specs)) 155 | 156 | builder := bytes.NewBuffer([]byte{}) 157 | 158 | for i, s := range specs { 159 | if s.Name != nil { 160 | builder.WriteString(s.Name.Name) 161 | builder.WriteRune(' ') 162 | } 163 | builder.WriteString(s.Path.Value) 164 | retval[i] = builder.String() 165 | builder.Reset() 166 | } 167 | return retval 168 | } 169 | 170 | // ListAsImportSpec returns the imports from the ImportBag as a slice of ImportSpecs 171 | // sorted alphabetically by their import paths. 172 | func (ib *ImportBag) ListAsImportSpec() []*ast.ImportSpec { 173 | retval := make([]*ast.ImportSpec, 0, len(ib.bySpec)+len(ib.localIdent)+len(ib.blankIdent)) 174 | 175 | getLit := func(pkgPath PackagePath) *ast.BasicLit { 176 | return &ast.BasicLit{ 177 | Kind: token.STRING, 178 | Value: fmt.Sprintf("%q", string(pkgPath)), 179 | } 180 | } 181 | 182 | for k, v := range ib.bySpec { 183 | var name *ast.Ident 184 | 185 | if path.Base(string(v)) != string(k) { 186 | name = ast.NewIdent(string(k)) 187 | } 188 | 189 | retval = append(retval, &ast.ImportSpec{ 190 | Name: name, 191 | Path: getLit(v), 192 | }) 193 | } 194 | 195 | for s := range ib.localIdent { 196 | retval = append(retval, &ast.ImportSpec{ 197 | Name: ast.NewIdent("."), 198 | Path: getLit(s), 199 | }) 200 | } 201 | 202 | for s := range ib.blankIdent { 203 | retval = append(retval, &ast.ImportSpec{ 204 | Name: ast.NewIdent("_"), 205 | Path: getLit(s), 206 | }) 207 | } 208 | 209 | sort.Slice(retval, func(i, j int) bool { 210 | return strings.Compare(retval[i].Path.Value, retval[j].Path.Value) < 0 211 | }) 212 | 213 | return retval 214 | } 215 | 216 | var impFinder = importer.Default() 217 | 218 | // FindSpecifier finds the name of a package by loading it in from GOPATH 219 | // or a vendor folder. 220 | func FindSpecifier(pkgPath PackagePath) (PackageSpecifier, error) { 221 | var pkg *types.Package 222 | var err error 223 | 224 | if cast, ok := impFinder.(types.ImporterFrom); ok { 225 | pkg, err = cast.ImportFrom(string(pkgPath), "", 0) 226 | } else { 227 | pkg, err = impFinder.Import(string(pkgPath)) 228 | } 229 | 230 | if err != nil { 231 | return "", err 232 | } 233 | return PackageSpecifier(pkg.Name()), nil 234 | } 235 | -------------------------------------------------------------------------------- /generators/eventgrid/static_templates.go: -------------------------------------------------------------------------------- 1 | // generated by github.com/Azure/buffalo-azure/generators/eventgrid/builder 2 | // DO NOT EDIT - instead run "go generate ./..." 3 | 4 | package eventgrid 5 | 6 | var staticTemplates = make(TemplateCache) 7 | 8 | func init() { 9 | staticTemplates["templates/actions/eventgrid_name.go.tmpl"] = []byte{112, 97, 99, 107, 97, 103, 101, 32, 97, 99, 116, 105, 111, 110, 115, 10, 10, 105, 109, 112, 111, 114, 116, 32, 40, 10, 123, 123, 32, 114, 97, 110, 103, 101, 32, 36, 105, 32, 58, 61, 32, 46, 105, 109, 112, 111, 114, 116, 115, 32, 125, 125, 9, 123, 123, 36, 105, 125, 125, 10, 123, 123, 32, 101, 110, 100, 32, 125, 125, 10, 41, 10, 10, 47, 47, 32, 77, 121, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 32, 103, 97, 116, 104, 101, 114, 115, 32, 114, 101, 115, 112, 111, 110, 100, 115, 32, 116, 111, 32, 97, 108, 108, 32, 82, 101, 113, 117, 101, 115, 116, 115, 32, 115, 101, 110, 116, 32, 116, 111, 32, 97, 32, 112, 97, 114, 116, 105, 99, 117, 108, 97, 114, 32, 101, 110, 100, 112, 111, 105, 110, 116, 46, 10, 116, 121, 112, 101, 32, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 32, 115, 116, 114, 117, 99, 116, 32, 123, 10, 9, 101, 103, 46, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 10, 125, 10, 10, 47, 47, 32, 78, 101, 119, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 32, 105, 110, 115, 116, 97, 110, 116, 105, 97, 116, 101, 115, 32, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 32, 102, 111, 114, 32, 117, 115, 101, 32, 105, 110, 32, 97, 32, 96, 98, 117, 102, 102, 97, 108, 111, 46, 65, 112, 112, 96, 46, 10, 102, 117, 110, 99, 32, 78, 101, 119, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 40, 112, 97, 114, 101, 110, 116, 32, 101, 103, 46, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 41, 32, 40, 99, 114, 101, 97, 116, 101, 100, 32, 42, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 41, 32, 123, 10, 9, 100, 105, 115, 112, 97, 116, 99, 104, 101, 114, 32, 58, 61, 32, 101, 103, 46, 78, 101, 119, 84, 121, 112, 101, 68, 105, 115, 112, 97, 116, 99, 104, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 40, 112, 97, 114, 101, 110, 116, 41, 10, 10, 9, 99, 114, 101, 97, 116, 101, 100, 32, 61, 32, 38, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 123, 10, 9, 9, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 58, 32, 100, 105, 115, 112, 97, 116, 99, 104, 101, 114, 44, 10, 9, 125, 10, 10, 123, 123, 32, 114, 97, 110, 103, 101, 32, 36, 116, 32, 58, 61, 32, 46, 116, 121, 112, 101, 115, 125, 125, 10, 9, 100, 105, 115, 112, 97, 116, 99, 104, 101, 114, 46, 66, 105, 110, 100, 40, 34, 123, 123, 36, 116, 46, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 125, 125, 34, 44, 32, 99, 114, 101, 97, 116, 101, 100, 46, 82, 101, 99, 101, 105, 118, 101, 123, 123, 36, 116, 46, 78, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 41, 10, 123, 123, 101, 110, 100, 125, 125, 10, 9, 100, 105, 115, 112, 97, 116, 99, 104, 101, 114, 46, 66, 105, 110, 100, 40, 101, 103, 46, 69, 118, 101, 110, 116, 84, 121, 112, 101, 87, 105, 108, 100, 99, 97, 114, 100, 44, 32, 99, 114, 101, 97, 116, 101, 100, 46, 82, 101, 99, 101, 105, 118, 101, 68, 101, 102, 97, 117, 108, 116, 41, 10, 10, 9, 114, 101, 116, 117, 114, 110, 10, 125, 10, 10, 123, 123, 32, 114, 97, 110, 103, 101, 32, 36, 116, 32, 58, 61, 32, 46, 116, 121, 112, 101, 115, 32, 125, 125, 10, 47, 47, 32, 82, 101, 99, 101, 105, 118, 101, 123, 123, 36, 116, 46, 78, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 32, 119, 105, 108, 108, 32, 114, 101, 115, 112, 111, 110, 100, 32, 116, 111, 32, 97, 110, 32, 96, 101, 118, 101, 110, 116, 103, 114, 105, 100, 46, 69, 118, 101, 110, 116, 96, 32, 99, 97, 114, 114, 121, 105, 110, 103, 32, 97, 32, 115, 101, 114, 105, 97, 108, 105, 122, 101, 100, 32, 96, 123, 123, 36, 116, 46, 78, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 96, 32, 97, 115, 32, 105, 116, 115, 32, 112, 97, 121, 108, 111, 97, 100, 46, 10, 102, 117, 110, 99, 32, 40, 115, 32, 42, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 41, 32, 82, 101, 99, 101, 105, 118, 101, 123, 123, 36, 116, 46, 78, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 40, 99, 32, 98, 117, 102, 102, 97, 108, 111, 46, 67, 111, 110, 116, 101, 120, 116, 44, 32, 101, 32, 101, 103, 46, 69, 118, 101, 110, 116, 41, 32, 101, 114, 114, 111, 114, 32, 123, 10, 9, 118, 97, 114, 32, 112, 97, 121, 108, 111, 97, 100, 32, 123, 123, 36, 116, 46, 80, 107, 103, 83, 112, 101, 99, 125, 125, 46, 123, 123, 36, 116, 46, 78, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 10, 9, 105, 102, 32, 101, 114, 114, 32, 58, 61, 32, 106, 115, 111, 110, 46, 85, 110, 109, 97, 114, 115, 104, 97, 108, 40, 101, 46, 68, 97, 116, 97, 44, 32, 38, 112, 97, 121, 108, 111, 97, 100, 41, 59, 32, 101, 114, 114, 32, 33, 61, 32, 110, 105, 108, 32, 123, 10, 9, 9, 114, 101, 116, 117, 114, 110, 32, 99, 46, 69, 114, 114, 111, 114, 40, 104, 116, 116, 112, 46, 83, 116, 97, 116, 117, 115, 66, 97, 100, 82, 101, 113, 117, 101, 115, 116, 44, 32, 101, 114, 114, 111, 114, 115, 46, 78, 101, 119, 40, 34, 117, 110, 97, 98, 108, 101, 32, 116, 111, 32, 117, 110, 109, 97, 114, 115, 104, 97, 108, 32, 114, 101, 113, 117, 101, 115, 116, 32, 100, 97, 116, 97, 34, 41, 41, 10, 9, 125, 10, 10, 9, 47, 47, 32, 82, 101, 112, 108, 97, 99, 101, 32, 116, 104, 101, 32, 99, 111, 100, 101, 32, 98, 101, 108, 111, 119, 32, 119, 105, 116, 104, 32, 121, 111, 117, 114, 32, 108, 111, 103, 105, 99, 10, 9, 114, 101, 116, 117, 114, 110, 32, 99, 46, 69, 114, 114, 111, 114, 40, 104, 116, 116, 112, 46, 83, 116, 97, 116, 117, 115, 73, 110, 116, 101, 114, 110, 97, 108, 83, 101, 114, 118, 101, 114, 69, 114, 114, 111, 114, 44, 32, 101, 114, 114, 111, 114, 115, 46, 78, 101, 119, 40, 34, 110, 111, 116, 32, 105, 109, 112, 108, 101, 109, 101, 110, 116, 101, 100, 34, 41, 41, 10, 125, 10, 123, 123, 101, 110, 100, 125, 125, 10, 10, 47, 47, 32, 82, 101, 99, 101, 105, 118, 101, 68, 101, 102, 97, 117, 108, 116, 32, 119, 105, 108, 108, 32, 114, 101, 115, 112, 111, 110, 100, 32, 116, 111, 32, 97, 110, 32, 96, 101, 118, 101, 110, 116, 103, 114, 105, 100, 46, 69, 118, 101, 110, 116, 96, 32, 99, 97, 114, 114, 121, 105, 110, 103, 32, 97, 110, 121, 32, 69, 118, 101, 110, 116, 84, 121, 112, 101, 32, 97, 115, 32, 105, 116, 115, 32, 112, 97, 121, 108, 111, 97, 100, 46, 10, 102, 117, 110, 99, 32, 40, 115, 32, 42, 123, 123, 36, 46, 110, 97, 109, 101, 46, 67, 97, 109, 101, 108, 125, 125, 83, 117, 98, 115, 99, 114, 105, 98, 101, 114, 41, 32, 82, 101, 99, 101, 105, 118, 101, 68, 101, 102, 97, 117, 108, 116, 40, 99, 32, 98, 117, 102, 102, 97, 108, 111, 46, 67, 111, 110, 116, 101, 120, 116, 44, 32, 101, 32, 101, 103, 46, 69, 118, 101, 110, 116, 41, 32, 101, 114, 114, 111, 114, 32, 123, 10, 9, 114, 101, 116, 117, 114, 110, 32, 99, 46, 69, 114, 114, 111, 114, 40, 104, 116, 116, 112, 46, 83, 116, 97, 116, 117, 115, 73, 110, 116, 101, 114, 110, 97, 108, 83, 101, 114, 118, 101, 114, 69, 114, 114, 111, 114, 44, 32, 101, 114, 114, 111, 114, 115, 46, 78, 101, 119, 40, 34, 110, 111, 116, 32, 105, 109, 112, 108, 101, 109, 101, 110, 116, 101, 100, 34, 41, 41, 10, 125, 10} 10 | } 11 | -------------------------------------------------------------------------------- /sdk/go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 3 | github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= 4 | github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 5 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 6 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 7 | github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= 8 | github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= 9 | github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= 10 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 12 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 14 | github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 15 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 16 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 17 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 18 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 19 | github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 20 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 21 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 22 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 23 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 24 | github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= 25 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 26 | github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= 27 | github.com/gobuffalo/buffalo v0.13.0 h1:Fyn55HJULJpFPMUNx9lrPK31qvr37+bpNGFbpAOauGI= 28 | github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4= 29 | github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs= 30 | github.com/gobuffalo/buffalo-plugins v1.0.4 h1:Q/7Vp93AYJDplMg7nCOI6+XEnTjMabu26WJgS7bU6Ew= 31 | github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4= 32 | github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8= 33 | github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc= 34 | github.com/gobuffalo/envy v1.6.5 h1:X3is06x7v0nW2xiy2yFbbIjwHz57CD6z6MkvqULTCm8= 35 | github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= 36 | github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw= 37 | github.com/gobuffalo/events v1.0.7 h1:WaIpqghQOUUdAE3W4LjmOnBvXvZw3XQjSnF1JGHVbMI= 38 | github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ= 39 | github.com/gobuffalo/fizz v1.0.12 h1:JJOkmlStog5AiBL434UoGMJ896p3MnTnzedFVaZSF3k= 40 | github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc= 41 | github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= 42 | github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= 43 | github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683 h1:uHrn7kCcU0zQ8snv1ogsNttxXZagNBr/gtYUf9LPr4Q= 44 | github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= 45 | github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g= 46 | github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= 47 | github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= 48 | github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= 49 | github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6 h1:zwOcFHdMbna9U85wdM0NMT+r2c98EDKwjIjZVwSwdVU= 50 | github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= 51 | github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I= 52 | github.com/gobuffalo/github_flavored_markdown v1.0.5 h1:YvGVf7yj1akgsb+qc64Q0WX8uhpuZSibChbqOMRSAqE= 53 | github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY= 54 | github.com/gobuffalo/httptest v1.0.2 h1:LWp2khlgA697h4BIYWW2aRxvB93jMnBrbakQ/r2KLzs= 55 | github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= 56 | github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= 57 | github.com/gobuffalo/makr v1.1.5 h1:lOlpv2iz0dNa4qse0ZYQgbtT+ybwVxWEAcOZbcPmeYc= 58 | github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw= 59 | github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 60 | github.com/gobuffalo/mapi v1.0.1 h1:JRuTiZzDEZhBHkFiHTxJkYRT6CbYuL0K/rn+1byJoEA= 61 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 62 | github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0= 63 | github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No= 64 | github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo= 65 | github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ= 66 | github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4= 67 | github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME= 68 | github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c= 69 | github.com/gobuffalo/packr v1.13.7 h1:2uZgLd6b/W4yRBZV/ScaORxZLNGMHO0VCvqQNkKukNA= 70 | github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24= 71 | github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= 72 | github.com/gobuffalo/plush v3.7.20+incompatible h1:FgLKw/zwd8IY8lAqfSuVNuHopR7jKVSs6yjJKeBajzU= 73 | github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= 74 | github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= 75 | github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= 76 | github.com/gobuffalo/pop v4.8.4+incompatible h1:5v15ZgICK3MFTU90QRqCaqDUf4wcriIbws1hqpYL2Xo= 77 | github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= 78 | github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= 79 | github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= 80 | github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA= 81 | github.com/gobuffalo/tags v2.0.11+incompatible h1:zLkaontB8lWefU+DX38mzPLRKFGTJL8FKb9JnKMt0Z0= 82 | github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= 83 | github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= 84 | github.com/gobuffalo/uuid v2.0.4+incompatible h1:m56JXViLiZaacKcx00GwfAc9dSLkp3akw/V6bOoJ1PA= 85 | github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= 86 | github.com/gobuffalo/validate v2.0.3+incompatible h1:6f4JCEz11Zi6iIlexMv7Jz10RBPvgI795AOaubtCwTE= 87 | github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= 88 | github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc= 89 | github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7 h1:N0iqtKwkicU8M2rLirTDJxdwuL8I2/8MjMlEayaNSgE= 90 | github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY= 91 | github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= 92 | github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 93 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 94 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 95 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 96 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 97 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 98 | github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= 99 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 100 | github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= 101 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 102 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 103 | github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 104 | github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= 105 | github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 106 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 107 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 108 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 109 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 110 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= 111 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= 112 | github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= 113 | github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= 114 | github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= 115 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= 116 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 117 | github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 118 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 119 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 120 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 121 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 122 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 123 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 124 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 125 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 126 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 127 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 128 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 129 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 130 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 131 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 132 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 133 | github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= 134 | github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= 135 | github.com/markbates/going v1.0.2 h1:uNQHDDfMRNOUmuxDbPbvatyw4wr4UOSUZkGkdkcip1o= 136 | github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= 137 | github.com/markbates/grift v1.0.4 h1:JjTyhlgPtgEnyHNvVn5lk21zWQbWD3cGE0YdyvvbZYg= 138 | github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= 139 | github.com/markbates/hmax v1.0.0 h1:yo2N0gBoCnUMKhV/VRLHomT6Y9wUm+oQQENuWJqCdlM= 140 | github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= 141 | github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= 142 | github.com/markbates/inflect v1.0.1 h1:t3WOiMLsNqn0Vvw87evC70WSawzHZcwMETn53rL1bBw= 143 | github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= 144 | github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 145 | github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 146 | github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc= 147 | github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 148 | github.com/markbates/refresh v1.4.10 h1:6EZ/vvVpWiam8OTIhrhfV9cVJR/NvScvcCiqosbTkbA= 149 | github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc= 150 | github.com/markbates/sigtx v1.0.0 h1:y/xtkBvNPRjD4KeEplf4w9rJVSc23/xl+jXYGowTwy0= 151 | github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= 152 | github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= 153 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 154 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 155 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 156 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 157 | github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= 158 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 159 | github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= 160 | github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= 161 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 162 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 163 | github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 164 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 165 | github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba h1:FEJJhVHSH+Kyxa5qNe/7dprlZbFcj2TG51OWIouhwls= 166 | github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= 167 | github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= 168 | github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= 169 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 170 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 171 | github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= 172 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 173 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 174 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 175 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 176 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 177 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 178 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 179 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 180 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 h1:ofR1ZdrNSkiWcMsRrubK9tb2/SlZVWttAfqUjJi6QYc= 181 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= 182 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 183 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 184 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= 185 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 186 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM= 187 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 188 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= 189 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 190 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b h1:vYEG87HxbU6dXj5npkeulCS96Dtz5xg3jcfCgpcvbIw= 191 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= 192 | github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8 h1:xLQlo0Ghg8zBaQi+tjpK+z/WLjbg/BhAWP9pYgqo/LQ= 193 | github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= 194 | github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9 h1:j3cAp1j8k/tSLaCcDiXIpVJ8FzSJ9g1eeOAPRJYM75k= 195 | github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= 196 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= 197 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 198 | github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 199 | github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 200 | github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= 201 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 202 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= 203 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 204 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= 205 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 206 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 207 | github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= 208 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 209 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 210 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 211 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 212 | github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= 213 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 214 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 215 | github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= 216 | github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= 217 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 218 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 219 | golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 220 | golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 221 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= 222 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 223 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 224 | golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 225 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 226 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 227 | golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 228 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 229 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= 230 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 231 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 232 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 233 | golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 234 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 235 | golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 236 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 237 | golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 238 | golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 239 | golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 240 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= 241 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 242 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 243 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 244 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 245 | golang.org/x/tools v0.0.0-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 246 | golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 247 | golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 248 | golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 249 | google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= 250 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 251 | gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 252 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= 253 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 254 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 255 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 256 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 257 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 258 | gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= 259 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= 260 | gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= 261 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 262 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 263 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 264 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 265 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/azure-sdk-for-go v18.0.0+incompatible h1:PD98+de2PG0lTTSKFJRkMO1ieGfjEuSrQYqad21ATzY= 2 | github.com/Azure/azure-sdk-for-go v18.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= 3 | github.com/Azure/go-autorest v10.12.0+incompatible h1:6YphwUK+oXbzvCc1fd5VrnxCekwzDkpA7gUEbci2MvI= 4 | github.com/Azure/go-autorest v10.12.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= 5 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 6 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 7 | github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= 8 | github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f h1:zvClvFQwU++UpIUBGC8YmDlfhUrweEy1R1Fj1gu5iIM= 9 | github.com/ajg/form v0.0.0-20160822230020-523a5da1a92f/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= 10 | github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= 11 | github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= 12 | github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c h1:2zRrJWIt/f9c9HhNHAgrRgq0San5gRRUJTBXLkchal0= 13 | github.com/cockroachdb/cockroach-go v0.0.0-20181001143604-e0a95dfd547c/go.mod h1:XGLbWH/ujMcbPbhZq52Nv6UrCghb1yGn//133kEsvDk= 14 | github.com/codegangsta/negroni v1.0.0/go.mod h1:v0y3T5G7Y1UlFfyxFn/QLRU4a2EuNau2iZY63YTKWo0= 15 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 16 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 17 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 18 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 19 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 20 | github.com/dustin/go-humanize v0.0.0-20180713052910-9f541cc9db5d/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 21 | github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= 22 | github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= 23 | github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= 24 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 25 | github.com/fatih/structs v1.0.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 26 | github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= 27 | github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= 28 | github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= 29 | github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= 30 | github.com/go-sql-driver/mysql v1.4.0 h1:7LxgVwFb2hIQtMm87NdgAVfXjnt4OePseqT1tKx+opk= 31 | github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= 32 | github.com/gobuffalo/buffalo v0.12.8-0.20181004233540-fac9bb505aa8/go.mod h1:sLyT7/dceRXJUxSsE813JTQtA3Eb1vjxWfo/N//vXIY= 33 | github.com/gobuffalo/buffalo v0.13.0 h1:Fyn55HJULJpFPMUNx9lrPK31qvr37+bpNGFbpAOauGI= 34 | github.com/gobuffalo/buffalo v0.13.0/go.mod h1:Mjn1Ba9wpIbpbrD+lIDMy99pQ0H0LiddMIIDGse7qT4= 35 | github.com/gobuffalo/buffalo-plugins v1.0.2/go.mod h1:pOp/uF7X3IShFHyobahTkTLZaeUXwb0GrUTb9ngJWTs= 36 | github.com/gobuffalo/buffalo-plugins v1.0.4 h1:Q/7Vp93AYJDplMg7nCOI6+XEnTjMabu26WJgS7bU6Ew= 37 | github.com/gobuffalo/buffalo-plugins v1.0.4/go.mod h1:pWS1vjtQ6uD17MVFWf7i3zfThrEKWlI5+PYLw/NaDB4= 38 | github.com/gobuffalo/buffalo-pop v1.0.5/go.mod h1:Fw/LfFDnSmB/vvQXPvcXEjzP98Tc+AudyNWUBWKCwQ8= 39 | github.com/gobuffalo/envy v1.6.4/go.mod h1:Abh+Jfw475/NWtYMEt+hnJWRiC8INKWibIMyNt1w2Mc= 40 | github.com/gobuffalo/envy v1.6.5 h1:X3is06x7v0nW2xiy2yFbbIjwHz57CD6z6MkvqULTCm8= 41 | github.com/gobuffalo/envy v1.6.5/go.mod h1:N+GkhhZ/93bGZc6ZKhJLP6+m+tCNPKwgSpH9kaifseQ= 42 | github.com/gobuffalo/events v1.0.3/go.mod h1:Txo8WmqScapa7zimEQIwgiJBvMECMe9gJjsKNPN3uZw= 43 | github.com/gobuffalo/events v1.0.7 h1:WaIpqghQOUUdAE3W4LjmOnBvXvZw3XQjSnF1JGHVbMI= 44 | github.com/gobuffalo/events v1.0.7/go.mod h1:z8txf6H9jWhQ5Scr7YPLWg/cgXBRj8Q4uYI+rsVCCSQ= 45 | github.com/gobuffalo/fizz v1.0.12 h1:JJOkmlStog5AiBL434UoGMJ896p3MnTnzedFVaZSF3k= 46 | github.com/gobuffalo/fizz v1.0.12/go.mod h1:C0sltPxpYK8Ftvf64kbsQa2yiCZY4RZviurNxXdAKwc= 47 | github.com/gobuffalo/flect v0.0.0-20180907193754-dc14d8acaf9f/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= 48 | github.com/gobuffalo/flect v0.0.0-20181002182613-4571df4b1daf/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= 49 | github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683 h1:uHrn7kCcU0zQ8snv1ogsNttxXZagNBr/gtYUf9LPr4Q= 50 | github.com/gobuffalo/flect v0.0.0-20181007231023-ae7ed6bfe683/go.mod h1:rCiQgmAE4axgBNl3jZWzS5rETRYTGOsrixTRaCPzNdA= 51 | github.com/gobuffalo/genny v0.0.0-20180924032338-7af3a40f2252/go.mod h1:tUTQOogrr7tAQnhajMSH6rv1BVev34H2sa1xNHMy94g= 52 | github.com/gobuffalo/genny v0.0.0-20181003150629-3786a0744c5d/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= 53 | github.com/gobuffalo/genny v0.0.0-20181005145118-318a41a134cc/go.mod h1:WAd8HmjMVrnkAZbmfgH5dLBUchsZfqzp/WS5sQz+uTM= 54 | github.com/gobuffalo/genny v0.0.0-20181007153042-b8de7d566757/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= 55 | github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6 h1:zwOcFHdMbna9U85wdM0NMT+r2c98EDKwjIjZVwSwdVU= 56 | github.com/gobuffalo/genny v0.0.0-20181012161047-33e5f43d83a6/go.mod h1:+oG5Ljrw04czAHbPXREwaFojJbpUvcIy4DiOnbEJFTA= 57 | github.com/gobuffalo/github_flavored_markdown v1.0.4/go.mod h1:uRowCdK+q8d/RF0Kt3/DSalaIXbb0De/dmTqMQdkQ4I= 58 | github.com/gobuffalo/github_flavored_markdown v1.0.5 h1:YvGVf7yj1akgsb+qc64Q0WX8uhpuZSibChbqOMRSAqE= 59 | github.com/gobuffalo/github_flavored_markdown v1.0.5/go.mod h1:U0643QShPF+OF2tJvYNiYDLDGDuQmJZXsf/bHOJPsMY= 60 | github.com/gobuffalo/httptest v1.0.2 h1:LWp2khlgA697h4BIYWW2aRxvB93jMnBrbakQ/r2KLzs= 61 | github.com/gobuffalo/httptest v1.0.2/go.mod h1:7T1IbSrg60ankme0aDLVnEY0h056g9M1/ZvpVThtB7E= 62 | github.com/gobuffalo/licenser v0.0.0-20180924033006-eae28e638a42/go.mod h1:Ubo90Np8gpsSZqNScZZkVXXAo5DGhTb+WYFIjlnog8w= 63 | github.com/gobuffalo/makr v1.1.5 h1:lOlpv2iz0dNa4qse0ZYQgbtT+ybwVxWEAcOZbcPmeYc= 64 | github.com/gobuffalo/makr v1.1.5/go.mod h1:Y+o0btAH1kYAMDJW/TX3+oAXEu0bmSLLoC9mIFxtzOw= 65 | github.com/gobuffalo/mapi v1.0.0/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 66 | github.com/gobuffalo/mapi v1.0.1 h1:JRuTiZzDEZhBHkFiHTxJkYRT6CbYuL0K/rn+1byJoEA= 67 | github.com/gobuffalo/mapi v1.0.1/go.mod h1:4VAGh89y6rVOvm5A8fKFxYG+wIW6LO1FMTG9hnKStFc= 68 | github.com/gobuffalo/mw-basicauth v1.0.3/go.mod h1:dg7+ilMZOKnQFHDefUzUHufNyTswVUviCBgF244C1+0= 69 | github.com/gobuffalo/mw-contenttype v0.0.0-20180802152300-74f5a47f4d56/go.mod h1:7EvcmzBbeCvFtQm5GqF9ys6QnCxz2UM1x0moiWLq1No= 70 | github.com/gobuffalo/mw-csrf v0.0.0-20180802151833-446ff26e108b/go.mod h1:sbGtb8DmDZuDUQoxjr8hG1ZbLtZboD9xsn6p77ppcHo= 71 | github.com/gobuffalo/mw-forcessl v0.0.0-20180802152810-73921ae7a130/go.mod h1:JvNHRj7bYNAMUr/5XMkZaDcw3jZhUZpsmzhd//FFWmQ= 72 | github.com/gobuffalo/mw-i18n v0.0.0-20180802152014-e3060b7e13d6/go.mod h1:91AQfukc52A6hdfIfkxzyr+kpVYDodgAeT5cjX1UIj4= 73 | github.com/gobuffalo/mw-paramlogger v0.0.0-20181005191442-d6ee392ec72e/go.mod h1:6OJr6VwSzgJMqWMj7TYmRUqzNe2LXu/W1rRW4MAz/ME= 74 | github.com/gobuffalo/mw-tokenauth v0.0.0-20181001105134-8545f626c189/go.mod h1:UqBF00IfKvd39ni5+yI5MLMjAf4gX7cDKN/26zDOD6c= 75 | github.com/gobuffalo/packr v1.13.7 h1:2uZgLd6b/W4yRBZV/ScaORxZLNGMHO0VCvqQNkKukNA= 76 | github.com/gobuffalo/packr v1.13.7/go.mod h1:KkinLIn/n6+3tVXMwg6KkNvWwVsrRAz4ph+jgpk3Z24= 77 | github.com/gobuffalo/plush v3.7.16+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= 78 | github.com/gobuffalo/plush v3.7.20+incompatible h1:FgLKw/zwd8IY8lAqfSuVNuHopR7jKVSs6yjJKeBajzU= 79 | github.com/gobuffalo/plush v3.7.20+incompatible/go.mod h1:rQ4zdtUUyZNqULlc6bqd5scsPfLKfT0+TGMChgduDvI= 80 | github.com/gobuffalo/pop v4.8.2+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= 81 | github.com/gobuffalo/pop v4.8.3+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= 82 | github.com/gobuffalo/pop v4.8.4+incompatible h1:5v15ZgICK3MFTU90QRqCaqDUf4wcriIbws1hqpYL2Xo= 83 | github.com/gobuffalo/pop v4.8.4+incompatible/go.mod h1:DwBz3SD5SsHpTZiTubcsFWcVDpJWGsxjVjMPnkiThWg= 84 | github.com/gobuffalo/release v1.0.35/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= 85 | github.com/gobuffalo/release v1.0.38/go.mod h1:VtHFAKs61vO3wboCec5xr9JPTjYyWYcvaM3lclkc4x4= 86 | github.com/gobuffalo/shoulders v1.0.1/go.mod h1:V33CcVmaQ4gRUmHKwq1fiTXuf8Gp/qjQBUL5tHPmvbA= 87 | github.com/gobuffalo/tags v2.0.11+incompatible h1:zLkaontB8lWefU+DX38mzPLRKFGTJL8FKb9JnKMt0Z0= 88 | github.com/gobuffalo/tags v2.0.11+incompatible/go.mod h1:9XmhOkyaB7UzvuY4UoZO4s67q8/xRMVJEaakauVQYeY= 89 | github.com/gobuffalo/uuid v2.0.3+incompatible h1:W83ymTRbzM+XNntIsjC8j63FyzGytcfKTudU1Cg6xyk= 90 | github.com/gobuffalo/uuid v2.0.3+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= 91 | github.com/gobuffalo/uuid v2.0.4+incompatible h1:m56JXViLiZaacKcx00GwfAc9dSLkp3akw/V6bOoJ1PA= 92 | github.com/gobuffalo/uuid v2.0.4+incompatible/go.mod h1:ErhIzkRhm0FtRuiE/PeORqcw4cVi1RtSpnwYrxuvkfE= 93 | github.com/gobuffalo/validate v2.0.3+incompatible h1:6f4JCEz11Zi6iIlexMv7Jz10RBPvgI795AOaubtCwTE= 94 | github.com/gobuffalo/validate v2.0.3+incompatible/go.mod h1:N+EtDe0J8252BgfzQUChBgfd6L93m9weay53EWFVsMM= 95 | github.com/gobuffalo/x v0.0.0-20181003152136-452098b06085/go.mod h1:WevpGD+5YOreDJznWevcn8NTmQEW5STSBgIkpkjzqXc= 96 | github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7 h1:N0iqtKwkicU8M2rLirTDJxdwuL8I2/8MjMlEayaNSgE= 97 | github.com/gobuffalo/x v0.0.0-20181007152206-913e47c59ca7/go.mod h1:9rDPXaB3kXdKWzMc4odGQQdG2e2DIEmANy5aSJ9yesY= 98 | github.com/gofrs/uuid v3.1.0+incompatible h1:q2rtkjaKT4YEr6E1kamy0Ha4RtepWlQBedyHx0uzKwA= 99 | github.com/gofrs/uuid v3.1.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= 100 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 101 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 102 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 103 | github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= 104 | github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= 105 | github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= 106 | github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= 107 | github.com/gorilla/pat v0.0.0-20180118222023-199c85a7f6d1/go.mod h1:YeAe0gNeiNT5hoiZRI4yiOky6jVdNvfO2N6Kav/HmxY= 108 | github.com/gorilla/securecookie v1.1.1 h1:miw7JPhV+b/lAHSXz4qd/nN9jRiAFV5FwjeKyCS8BvQ= 109 | github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= 110 | github.com/gorilla/sessions v1.1.2/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 111 | github.com/gorilla/sessions v1.1.3 h1:uXoZdcdA5XdXF3QzuSlheVRUvjl+1rKY7zBXL68L9RU= 112 | github.com/gorilla/sessions v1.1.3/go.mod h1:8KCfur6+4Mqcc6S0FEfKuN15Vl5MgXW92AE8ovaJD0w= 113 | github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= 114 | github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= 115 | github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= 116 | github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= 117 | github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= 118 | github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= 119 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733 h1:vr3AYkKovP8uR8AvSGGUK1IDqRa5lAAvEkZG1LKaCRc= 120 | github.com/jackc/fake v0.0.0-20150926172116-812a484cc733/go.mod h1:WrMFNQdiFJ80sQsxDoMokWK1W5TQtxBFNpzWTD84ibQ= 121 | github.com/jackc/pgx v3.2.0+incompatible h1:0Vihzu20St42/UDsvZGdNE6jak7oi/UOeMzwMPHkgFY= 122 | github.com/jackc/pgx v3.2.0+incompatible/go.mod h1:0ZGrqGqkRlliWnWB4zKnWtjbSWbGkVEFm4TeybAXq+I= 123 | github.com/jmoiron/sqlx v0.0.0-20180614180643-0dae4fefe7c0/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU= 124 | github.com/jmoiron/sqlx v1.2.0 h1:41Ip0zITnmWNR/vHV+S4m+VoUivnWY5E4OJfLZjCJMA= 125 | github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= 126 | github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 127 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 128 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 129 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= 130 | github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= 131 | github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 132 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 133 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 134 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 135 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 136 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 137 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 138 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 139 | github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= 140 | github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 141 | github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY= 142 | github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= 143 | github.com/markbates/deplist v1.0.4/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= 144 | github.com/markbates/deplist v1.0.5/go.mod h1:gRRbPbbuA8TmMiRvaOzUlRfzfjeCCBqX2A6arxN01MM= 145 | github.com/markbates/going v1.0.2 h1:uNQHDDfMRNOUmuxDbPbvatyw4wr4UOSUZkGkdkcip1o= 146 | github.com/markbates/going v1.0.2/go.mod h1:UWCk3zm0UKefHZ7l8BNqi26UyiEMniznk8naLdTcy6c= 147 | github.com/markbates/grift v1.0.4 h1:JjTyhlgPtgEnyHNvVn5lk21zWQbWD3cGE0YdyvvbZYg= 148 | github.com/markbates/grift v1.0.4/go.mod h1:wbmtW74veyx+cgfwFhlnnMWqhoz55rnHR47oMXzsyVs= 149 | github.com/markbates/hmax v1.0.0 h1:yo2N0gBoCnUMKhV/VRLHomT6Y9wUm+oQQENuWJqCdlM= 150 | github.com/markbates/hmax v1.0.0/go.mod h1:cOkR9dktiESxIMu+65oc/r/bdY4bE8zZw3OLhLx0X2c= 151 | github.com/markbates/inflect v1.0.0/go.mod h1:oTeZL2KHA7CUX6X+fovmK9OvIOFuqu0TwdQrZjLTh88= 152 | github.com/markbates/inflect v1.0.1 h1:t3WOiMLsNqn0Vvw87evC70WSawzHZcwMETn53rL1bBw= 153 | github.com/markbates/inflect v1.0.1/go.mod h1:uv3UVNBe5qBIfCm8O8Q+DW+S1EopeyINj+Ikhc7rnCk= 154 | github.com/markbates/oncer v0.0.0-20180924031910-e862a676800b/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 155 | github.com/markbates/oncer v0.0.0-20180924034138-723ad0170a46/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 156 | github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4 h1:Mlji5gkcpzkqTROyE4ZxZ8hN7osunMb2RuGVrbvMvCc= 157 | github.com/markbates/oncer v0.0.0-20181014194634-05fccaae8fc4/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE= 158 | github.com/markbates/refresh v1.4.10 h1:6EZ/vvVpWiam8OTIhrhfV9cVJR/NvScvcCiqosbTkbA= 159 | github.com/markbates/refresh v1.4.10/go.mod h1:NDPHvotuZmTmesXxr95C9bjlw1/0frJwtME2dzcVKhc= 160 | github.com/markbates/sigtx v1.0.0 h1:y/xtkBvNPRjD4KeEplf4w9rJVSc23/xl+jXYGowTwy0= 161 | github.com/markbates/sigtx v1.0.0/go.mod h1:QF1Hv6Ic6Ca6W+T+DL0Y/ypborFKyvUY9HmuCD4VeTc= 162 | github.com/markbates/willie v1.0.9/go.mod h1:fsrFVWl91+gXpx/6dv715j7i11fYPfZ9ZGfH0DQzY7w= 163 | github.com/marstr/collection v0.3.3 h1:4Byf30YGmQXBrwqVbM98NZMUolO4Flz1Fv94Vs9dyi4= 164 | github.com/marstr/collection v0.3.3/go.mod h1:HHDXVxjLO3UYCBXJWY+J/ZrxCUOYqrO66ob1AzIsmYA= 165 | github.com/marstr/randname v0.0.0-20180611202505-48a63b6052f1 h1:Vpf5uI6fXEOFDxs+vjrY+p8mQxAkABiCErSa37vqbsI= 166 | github.com/marstr/randname v0.0.0-20180611202505-48a63b6052f1/go.mod h1:LbCPCzMi2EWPoqRiJR/yD680uiz05T8BPSoZWaS/5AM= 167 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 168 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 169 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 170 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 171 | github.com/mattn/go-sqlite3 v1.9.0 h1:pDRiWfl+++eC2FEFRy6jXmQlvp4Yh3z1MJKg4UeYM/4= 172 | github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= 173 | github.com/microcosm-cc/bluemonday v1.0.1 h1:SIYunPjnlXcW+gVfvm0IlSeR5U3WZUOLfVmqg85Go44= 174 | github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= 175 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 176 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 177 | github.com/mitchellh/mapstructure v1.0.0/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 178 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 179 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 180 | github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba h1:FEJJhVHSH+Kyxa5qNe/7dprlZbFcj2TG51OWIouhwls= 181 | github.com/monoculum/formam v0.0.0-20180901015400-4e68be1d79ba/go.mod h1:RKgILGEJq24YyJ2ban8EO0RUVSJlF1pGsEvoLEACr/Q= 182 | github.com/nicksnyder/go-i18n v1.10.0/go.mod h1:HrK7VCrbOvQoUAQ7Vpy7i87N7JZZZ7R2xBGjv0j365Q= 183 | github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw= 184 | github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= 185 | github.com/onsi/gomega v1.4.1/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= 186 | github.com/onsi/gomega v1.4.2 h1:3mYCb7aPxS/RU7TI1y4rkEn1oKmPRjNJLNEXgw7MH2I= 187 | github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= 188 | github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= 189 | github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= 190 | github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw= 191 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 192 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 193 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 194 | github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= 195 | github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= 196 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516 h1:ofR1ZdrNSkiWcMsRrubK9tb2/SlZVWttAfqUjJi6QYc= 197 | github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= 198 | github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= 199 | github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= 200 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t43dCZ5dqY4YTJCObLY1kIXl0uzMv+7DE= 201 | github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= 202 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e h1:MZM7FHLqUHYI0Y/mQAt3d2aYa0SiNms/hFqC9qJYolM= 203 | github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= 204 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041 h1:llrF3Fs4018ePo4+G/HV/uQUqEI1HMDjCeOf2V6puPc= 205 | github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= 206 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b h1:vYEG87HxbU6dXj5npkeulCS96Dtz5xg3jcfCgpcvbIw= 207 | github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= 208 | github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8 h1:xLQlo0Ghg8zBaQi+tjpK+z/WLjbg/BhAWP9pYgqo/LQ= 209 | github.com/shurcooL/highlight_go v0.0.0-20170515013102-78fb10f4a5f8/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= 210 | github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9 h1:j3cAp1j8k/tSLaCcDiXIpVJ8FzSJ9g1eeOAPRJYM75k= 211 | github.com/shurcooL/octicon v0.0.0-20180602230221-c42b0e3b24d9/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= 212 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95 h1:/vdW8Cb7EXrkqWGufVMES1OH2sU9gKVb2n9/1y5NMBY= 213 | github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 214 | github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 215 | github.com/sirupsen/logrus v1.1.0/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 216 | github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg= 217 | github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A= 218 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d h1:yKm7XZV6j9Ev6lojP2XaIshpT4ymkqhMeSghO5Ps00E= 219 | github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= 220 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e h1:qpG93cPwA5f7s/ZPBJnGOYQNK/vKsaDaseuKT5Asee8= 221 | github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= 222 | github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI= 223 | github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= 224 | github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= 225 | github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= 226 | github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= 227 | github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= 228 | github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= 229 | github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= 230 | github.com/spf13/pflag v1.0.2/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 231 | github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= 232 | github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= 233 | github.com/spf13/viper v1.2.1 h1:bIcUwXqLseLF3BDAZduuNfekWG87ibtFxi59Bq+oI9M= 234 | github.com/spf13/viper v1.2.1/go.mod h1:P4AexN0a+C9tGAnUFNwDMYYZv3pjFuvmeiMyKRaNVlI= 235 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 236 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 237 | github.com/unrolled/secure v0.0.0-20180918153822-f340ee86eb8b/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= 238 | github.com/unrolled/secure v0.0.0-20181005190816-ff9db2ff917f/go.mod h1:mnPT77IAdsi/kV7+Es7y+pXALeV3h7G6dQF6mNYjcLA= 239 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 240 | golang.org/x/crypto v0.0.0-20180910181607-0e37d006457b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 241 | golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 242 | golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc= 243 | golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 244 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e h1:IzypfodbhbnViNUO/MEh0FzCUooG97cIGfdggUrUSyU= 245 | golang.org/x/crypto v0.0.0-20181015023909-0c41d7ab0a0e/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 246 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 247 | golang.org/x/net v0.0.0-20180816102801-aaf60122140d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 248 | golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 249 | golang.org/x/net v0.0.0-20180921000356-2f5d2388922f/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 250 | golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 251 | golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 252 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= 253 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 254 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 255 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 256 | golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 257 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 258 | golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 259 | golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 260 | golang.org/x/sys v0.0.0-20180921163948-d47a0f339242/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 261 | golang.org/x/sys v0.0.0-20180927150500-dad3d9fb7b6e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 262 | golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e h1:EfdBzeKbFSvOjoIqSZcfS8wp0FBLokGBEs9lz1OtSg0= 263 | golang.org/x/sys v0.0.0-20181005133103-4497e2df6f9e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 264 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba h1:nZJIJPGow0Kf9bU9QTc1U6OXbs/7Hu4e+cNv+hxH+Zc= 265 | golang.org/x/sys v0.0.0-20181011152604-fa43e7bc11ba/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 266 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 267 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 268 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 269 | golang.org/x/tools v0.0.0-20181003024731-2f84ea8ef872/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 270 | golang.org/x/tools v0.0.0-20181006002542-f60d9635b16a/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 271 | golang.org/x/tools v0.0.0-20181008205924-a2b3f7f249e9/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 272 | golang.org/x/tools v0.0.0-20181013182035-5e66757b835f/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 273 | google.golang.org/appengine v1.2.0 h1:S0iUepdCWODXRvtE+gcRDd15L+k+k1AiHlMiMjefH24= 274 | google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 275 | gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= 276 | gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= 277 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 278 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 279 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 280 | gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= 281 | gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= 282 | gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= 283 | gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df/go.mod h1:LRQQ+SO6ZHR7tOkpBDuZnXENFzX8qRjMDMyPD6BRkCw= 284 | gopkg.in/mail.v2 v2.0.0-20180731213649-a0242b2233b4/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= 285 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= 286 | gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= 287 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 288 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 289 | -------------------------------------------------------------------------------- /cmd/provision.go: -------------------------------------------------------------------------------- 1 | // Copyright © 2018 Microsoft Corporation and contributors 2 | // 3 | // Permission is hereby granted, free of charge, to any person obtaining a copy 4 | // of this software and associated documentation files (the "Software"), to deal 5 | // in the Software without restriction, including without limitation the rights 6 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | // copies of the Software, and to permit persons to whom the Software is 8 | // furnished to do so, subject to the following conditions: 9 | // 10 | // The above copyright notice and this permission notice shall be included in 11 | // all copies or substantial portions of the Software. 12 | // 13 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | // THE SOFTWARE. 20 | 21 | package cmd 22 | 23 | import ( 24 | "bytes" 25 | "context" 26 | "encoding/json" 27 | "errors" 28 | "fmt" 29 | "io" 30 | "io/ioutil" 31 | "net/http" 32 | "net/url" 33 | "os" 34 | "strings" 35 | "time" 36 | 37 | "github.com/Azure/azure-sdk-for-go/profiles/latest/resources/mgmt/subscriptions" 38 | "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2017-05-10/resources" 39 | "github.com/Azure/go-autorest/autorest" 40 | "github.com/Azure/go-autorest/autorest/adal" 41 | "github.com/Azure/go-autorest/autorest/azure" 42 | "github.com/gobuffalo/buffalo/meta" 43 | "github.com/gobuffalo/pop" 44 | "github.com/joho/godotenv" 45 | "github.com/marstr/randname" 46 | "github.com/sirupsen/logrus" 47 | "github.com/spf13/cobra" 48 | "github.com/spf13/viper" 49 | ) 50 | 51 | // clientID is used to identify this application during a Device Auth flow. 52 | // We have deliberately spoofed as the Azure CLI 2.0 at least temporarily. 53 | const deviceClientID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46" 54 | 55 | var provisionConfig = viper.New() 56 | 57 | var userAgent string 58 | 59 | // These constants define a parameter which allows control of the name of the site that is to be created. 60 | const ( 61 | SiteName = "site-name" 62 | SiteShorthand = "n" 63 | siteDefaultPrefix = "buffalo-app" 64 | siteDefaultMessage = siteDefaultPrefix + "-" 65 | siteUsage = "The name of the site that will be deployed to Azure." 66 | ) 67 | 68 | // These constants define a parameter which gives control over the Azure Resource Group that should be used to hold 69 | // the created assets. 70 | const ( 71 | ResoureGroupName = "resource-group" 72 | ResourceGroupShorthand = "g" 73 | ResourceGroupDefault = "" 74 | resourceGroupUsage = "The name of the Resource Group that should hold the resources created." 75 | ) 76 | 77 | // These constants define a parameter which allows control over the Azure Region that should be used when creating a 78 | // resource group. If the specified resource group already exists, its location is used and this parameter is discarded. 79 | const ( 80 | LocationName = "location" 81 | LocationShorthand = "l" 82 | LocationDefault = "centralus" 83 | LocationDefaultText = ", or " + LocationDefault 84 | locationUsage = "The Azure Region that should be used when creating a resource group." 85 | ) 86 | 87 | // These constants define a parameter that allows control of the type of database to be provisioned. This is largely an 88 | // escape hatch to use if Buffalo-Azure is incorrectly identifying the flavor of database to use after reading your 89 | // application. 90 | // 91 | // Supported flavors: 92 | // - None 93 | // - Postgresql 94 | // - MySQL 95 | const ( 96 | DatabaseTypeName = "db-type" 97 | DatabaseShorthand = "d" 98 | databaseUsage = "The type of database to provision." 99 | ) 100 | 101 | // These constants define a parameter which gives control of the name of the database to be connected to by the 102 | // Buffalo application. 103 | const ( 104 | DatabaseNameName = "db-name" 105 | databaseNameUsage = "The name of the database to be connected to by the Buffalo application once its deployed." 106 | ) 107 | 108 | // These constants defeine a parameter which controls the password of the 109 | const ( 110 | DatabaseAdminName = "db-admin" 111 | DatabaseAdminDefault = "buffaloAdmin" 112 | databaseAdminUsage = "The user handle of the administrator account." 113 | ) 114 | 115 | // These constants define a parameter which controls the password of the database provisioned. For marginal security, 116 | // it is randomly generated each time `buffalo azure provision` is run. It will be visibile in the "connection strings" 117 | // of your App Service. 118 | const ( 119 | DatabasePasswordName = "db-password" 120 | DatabasePasswordShorthand = "w" 121 | DatabasePasswordDefault = "" 122 | databasePasswordUsage = "The administrator password for the database created. It is recommended you read this from a file instead of typing it in from your terminal." 123 | DatabasePasswordEnvVar = "BUFFALO_AZURE_DATABASE_PASSWORD" 124 | ) 125 | 126 | // These constants define a parameter which allows control over the particular Azure cloud which should be used for 127 | // deployment. 128 | // Some examples of Azure environments by name include: 129 | // - AzurePublicCloud (most popular) 130 | // - AzureChinaCloud 131 | // - AzureGermanCloud 132 | // - AzureUSGovernmentCloud 133 | const ( 134 | EnvironmentName = "az-env" 135 | EnvironmentShorthand = "a" 136 | EnvironmentDefault = "AzurePublicCloud" 137 | environmentUsage = "The Azure environment that will be targeted for deployment." 138 | ) 139 | 140 | var environment azure.Environment 141 | 142 | // These constants define a parameter which will control which container image is used to 143 | const ( 144 | // ImageName is the full parameter name of the argument that controls which container image will be used 145 | // when the Web App for Containers is provisioned. 146 | ImageName = "image" 147 | 148 | // ImageShorthand is the abbreviated means of using ImageName. 149 | ImageShorthand = "i" 150 | 151 | // ImageDefault is the container image that will be deployed if you didn't specify one. 152 | ImageDefault = "appsvc/sample-hello-world:latest" 153 | 154 | imageUsage = "The container image that defines this project." 155 | ) 156 | 157 | // These constants define a parameter that allows control of the Azure Resource Management (ARM) template that should be 158 | // used to provision infrastructure. This tool is not designed to deploy arbitrary ARM templates, rather this parameter 159 | // is intended to give you the flexibility to lock to a known version of the gobuffalo quick start template, or tweak 160 | // that template a little for your own usage. 161 | // 162 | // To prevent live-site incidents, a local copy of the default template is stored in this executable. If this parameter 163 | // is NOT specified, this program will attempt to acquire the remote version of the default-template. Should that fail, 164 | // the locally cached copy will be used. If the parameter is specified, this program will attempt to acquire the remote 165 | // version. If that operation fails, the program does NOT use the cached template, and terminates with a non-zero exit 166 | // status. 167 | const ( 168 | // TemplateName is the full parameter name of the argument providing a URL where the ARM template to bue used can 169 | // be found. 170 | TemplateName = "rm-template" 171 | 172 | // TemplateShorthand is the abbreviated means of using TemplateName. 173 | TemplateShorthand = "t" 174 | 175 | // TemplateDefault is the name of the Template to use if no value was provided. 176 | TemplateDefault = "./azuredeploy.json" 177 | 178 | // TemplateDefaultLink defines the link that will be used if no local rm-template is found, and a link wasn't 179 | // provided. 180 | TemplateDefaultLink = "https://aka.ms/buffalo-template" 181 | templateUsage = "The Azure Resource Management template which specifies the resources to provision." 182 | ) 183 | 184 | // These constants define a parameter which allows control of the ARM template parameters that should be used during 185 | // deployment. 186 | const ( 187 | TemplateParametersName = "rm-template-params" 188 | TemplateParametersShorthand = "p" 189 | TemplateParametersDefault = "./azuredeploy.parameters.json" 190 | templateParametersUsage = "The parameters that should be provided when creating a deployment." 191 | ) 192 | 193 | // These constants define a parameter that Azure subscription to own the resources created. 194 | // 195 | // This can also be specified with the environment variable AZURE_SUBSCRIPTION_ID or AZ_SUBSCRIPTION_ID. 196 | const ( 197 | SubscriptionName = "subscription-id" 198 | SubscriptionShorthand = "s" 199 | subscriptionUsage = "The ID (in UUID format) of the Azure subscription which should host the provisioned resources." 200 | ) 201 | 202 | // These constants define a parameter which will control the profile being used for the sake of connections. 203 | const ( 204 | ProfileName = "buffalo-env" 205 | ProfileShorthand = "b" 206 | profileUsage = "The buffalo environment that should be used." 207 | ) 208 | 209 | // These constants define a parameter which allows specification of a Service Principal for authentication. 210 | // This should always be used in tandem with `--client-secret`. 211 | // 212 | // This can also be specified with the environment variable AZURE_CLIENT_ID or AZ_CLIENT_ID. 213 | // 214 | // To learn more about getting started with Service Principals you can look here: 215 | // - Using the Azure CLI 2.0: [https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli](https://docs.microsoft.com/en-us/cli/azure/create-an-azure-service-principal-azure-cli?toc=%2Fazure%2Fazure-resource-manager%2Ftoc.json&view=azure-cli-latest) 216 | // - Using Azure PowerShell: [https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal?view=azure-cli-latest) 217 | // - Using the Azure Portal: [https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal](https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal?view=azure-cli-latest) 218 | const ( 219 | ClientIDName = "client-id" 220 | clientIDUsage = "The Application ID of the App Registration being used to authenticate." 221 | ) 222 | 223 | // These constants define a parameter which allows specification of a Service Principal for authentication. 224 | // This should always be used in tandem with `--client-id`. 225 | // 226 | // This can also be specified with the environment variable AZURE_CLIENT_SECRET or AZ_CLIENT_SECRET. 227 | const ( 228 | ClientSecretName = "client-secret" 229 | clientSecretUsage = "The Key associated with the App Registration being used to authenticate." 230 | ) 231 | 232 | // These constants define a parameter which provides the organization that should be used during authentication. 233 | // Providing the tenant-id explicitly can help speed up execution, but by default this program will traverse all tenants 234 | // available to the authenticated identity (service principal or user), and find the one containing the subscription 235 | // provided. This traversal may involve several HTTP requests, and is therefore somewhat latent. 236 | // 237 | // This can also be specified with the environment variable AZURE_TENANT_ID or AZ_TENANT_ID. 238 | const ( 239 | TenantIDName = "tenant-id" 240 | tenantUsage = "The ID (in form of a UUID) of the organization that the identity being used belongs to. " 241 | ) 242 | 243 | // These constants define a parameter which forces this program to ignore any ambient Azure settings available as 244 | // environment variables, and instead forces us to use Device Auth instead. 245 | const ( 246 | DeviceAuthName = "use-device-auth" 247 | deviceAuthUsage = "Ignore --client-id and --client-secret, interactively authenticate instead." 248 | ) 249 | 250 | // These constants define a parameter which toggles whether or not status information will be printed as this program 251 | // executes. 252 | const ( 253 | VerboseName = "verbose" 254 | VerboseShortname = "v" 255 | verboseUsage = "Print out status information as this program executes." 256 | ) 257 | 258 | // These constants define a parameter which toggles whether or not to save the template used for deployment to disk. 259 | const ( 260 | SkipTemplateCacheName = "skip-template-cache" 261 | SkipTemplateCacheShorthand = "z" 262 | skipTemplateCacheUsage = "After downloading the default template, do NOT save it in the working directory." 263 | ) 264 | 265 | // These constants define a parameter which toggles whether or not to save the parameters used for deployment to disk. 266 | const ( 267 | SkipParameterCacheName = "skip-parameters-cache" 268 | SkipParameterCacheShorthand = "y" 269 | skipParameterCacheUsage = "After creating deploying the site, do NOT save the parameters that were used for deployment." 270 | ) 271 | 272 | // These constants define a parameter which toggles whether or not a deployment is actually started. 273 | const ( 274 | SkipDeploymentName = "skip-deployment" 275 | SkipDeploymentShorthand = "x" 276 | skipDeploymentUsage = "Do not create an Azure deployment, do just the meta tasks." 277 | ) 278 | 279 | // These constants define a parameter which controls the Docker registry that will be searched for the image provided. 280 | const ( 281 | DockerRegistryURLName = "docker-registry-url" 282 | dockerRegistryURLUsage = "The URL for a private Docker Registry containing the image definition." 283 | ) 284 | 285 | // These constants define a parameter which allows the username to be set for Docker authentication. 286 | const ( 287 | DockerRegistryUsernameName = "docker-registry-username" 288 | dockerRegistryUsernameUsage = "The user handle to allow access to a private docker registry." 289 | ) 290 | 291 | // These constants define a parameter which allows the password to be set for Docker authentication. 292 | const ( 293 | DockerRegistryPasswordName = "docker-registry-password" 294 | dockerRegistryPasswordUsage = "The user password to allow access to a private docker registry." 295 | DockerRegistryPasswordEnvVar = "BUFFALO_AZURE_DOCKER_PASSWORD" 296 | ) 297 | 298 | // DockerAccess is an enum that contains either "private" or "public" 299 | type DockerAccess string 300 | 301 | // All (currently) possible value of DockerAccess 302 | const ( 303 | DockerAccessPublic = "public" 304 | DockerAccessPrivate = "private" 305 | ) 306 | 307 | // These constants define a parameter which informs buffalo-azure whether the registry is public or 308 | // private. 309 | const ( 310 | DockerRegistryAccessName = "docker-registry-access" 311 | DockerRegistryAccessDefault = DockerAccessPublic 312 | dockerRegistryAccessUsage = "Specifies whether the Docker registry targeted is " + DockerAccessPublic + " or " + DockerAccessPrivate 313 | ) 314 | 315 | var log = logrus.New() 316 | 317 | var debug string 318 | 319 | var deployParams *DeploymentParameters 320 | 321 | // provisionCmd represents the provision command 322 | var provisionCmd = &cobra.Command{ 323 | Aliases: []string{"p"}, 324 | Use: "provision", 325 | Short: "Create the infrastructure necessary to run a buffalo app on Azure.", 326 | Run: func(cmd *cobra.Command, args []string) { 327 | // Authenticate and setup clients 328 | subscriptionID := provisionConfig.GetString(SubscriptionName) 329 | clientID := provisionConfig.GetString(ClientIDName) 330 | clientSecret := provisionConfig.GetString(ClientSecretName) 331 | templateLocation := provisionConfig.GetString(TemplateName) 332 | image := provisionConfig.GetString(ImageName) 333 | databaseType := provisionConfig.GetString(DatabaseTypeName) 334 | databaseName := provisionConfig.GetString(DatabaseNameName) 335 | databaseAdmin := provisionConfig.GetString(DatabaseAdminName) 336 | siteName := provisionConfig.GetString(SiteName) 337 | 338 | ctx, cancel := context.WithTimeout(context.Background(), 45*time.Minute) 339 | defer cancel() 340 | 341 | var err error 342 | var auth autorest.Authorizer 343 | 344 | if !provisionConfig.GetBool(SkipDeploymentName) { 345 | auth, err = getAuthorizer(ctx, subscriptionID, clientID, clientSecret, provisionConfig.GetString(TenantIDName)) 346 | if err != nil { 347 | log.Error("unable to authenticate: ", err) 348 | return 349 | } 350 | } 351 | 352 | log.Debug(TenantIDName+" selected: ", provisionConfig.GetString(TenantIDName)) 353 | log.Debug(SubscriptionName+" selected: ", subscriptionID) 354 | log.Debug(TemplateName+" selected: ", templateLocation) 355 | log.Debug(DatabaseTypeName+" selected: ", databaseType) 356 | log.Debug(DatabaseNameName+" selected: ", databaseName) 357 | log.Debug(DatabaseAdminName+" selected: ", databaseAdmin) 358 | 359 | if usingDB, dbPassword := !strings.EqualFold(provisionConfig.GetString(DatabaseTypeName), "none"), provisionConfig.GetString(DatabasePasswordName); usingDB && dbPassword == DatabasePasswordDefault { 360 | newPass := randname.GenerateWithPrefix("MSFT+Buffalo-", 20) 361 | provisionConfig.Set(DatabasePasswordName, newPass) 362 | log.Debug("generated database password") 363 | } else if usingDB { 364 | log.Debug("using provided password") 365 | } 366 | 367 | var envMap map[string]string 368 | const envFileLoc = "./.env" 369 | if envMap, err = godotenv.Read(envFileLoc); err != nil { 370 | envMap = make(map[string]string, 1) 371 | } 372 | 373 | envMap[DatabasePasswordEnvVar] = provisionConfig.GetString(DatabasePasswordName) 374 | envMap[DockerRegistryPasswordEnvVar] = provisionConfig.GetString(DockerRegistryPasswordName) 375 | 376 | if err := godotenv.Write(envMap, envFileLoc); err == nil { 377 | log.Debugf("wrote passwords to %q", envFileLoc) 378 | } else { 379 | log.Errorf("unable to passwords to %q", envFileLoc) 380 | } 381 | 382 | log.Debug(ImageName+" selected: ", image) 383 | 384 | // Provision the necessary assets. 385 | 386 | deployParams.Parameters["name"] = DeploymentParameter{siteName} 387 | deployParams.Parameters["database"] = DeploymentParameter{strings.ToLower(databaseType)} 388 | deployParams.Parameters["databaseName"] = DeploymentParameter{databaseName} 389 | deployParams.Parameters["imageName"] = DeploymentParameter{image} 390 | deployParams.Parameters["databaseAdministratorLogin"] = DeploymentParameter{databaseAdmin} 391 | deployParams.Parameters["databaseAdministratorLoginPassword"] = DeploymentParameter{provisionConfig.GetString(DatabasePasswordName)} 392 | deployParams.Parameters["dockerRegistryAccess"] = DeploymentParameter{provisionConfig.GetString(DockerRegistryAccessName)} 393 | deployParams.Parameters["dockerRegistryServerURL"] = DeploymentParameter{provisionConfig.GetString(DockerRegistryURLName)} 394 | deployParams.Parameters["dockerRegistryServerUsername"] = DeploymentParameter{provisionConfig.GetString(DockerRegistryUsernameName)} 395 | deployParams.Parameters["dockerRegistryServerPassword"] = DeploymentParameter{provisionConfig.GetString(DockerRegistryPasswordName)} 396 | 397 | template, err := getDeploymentTemplate(ctx, templateLocation) 398 | if err != nil { 399 | log.Error("unable to fetch template: ", err) 400 | return 401 | } 402 | 403 | template.Parameters = deployParams.Parameters 404 | template.Mode = resources.Incremental 405 | 406 | deploymentResults := make(chan error) 407 | if provisionConfig.GetBool(SkipDeploymentName) { 408 | close(deploymentResults) 409 | } else { 410 | go func(errOut chan<- error) { 411 | defer close(errOut) 412 | groups := resources.NewGroupsClient(subscriptionID) 413 | groups.Authorizer = auth 414 | groups.AddToUserAgent(userAgent) 415 | 416 | // Assert the presence of the specified Resource Group 417 | rgName := provisionConfig.GetString(ResoureGroupName) 418 | created, err := insertResourceGroup(ctx, groups, rgName, provisionConfig.GetString(LocationName)) 419 | if err != nil { 420 | log.Errorf("unable to fetch or create resource group %s: %v\n", rgName, err) 421 | errOut <- err 422 | return 423 | } 424 | if created { 425 | log.Info("created resource group: ", rgName) 426 | } else { 427 | log.Info("found resource group: ", rgName) 428 | } 429 | log.Debug("site name selected: ", siteName) 430 | 431 | pLink := portalLink(subscriptionID, rgName) 432 | 433 | log.Info("beginning deployment") 434 | if err := doDeployment(ctx, auth, subscriptionID, rgName, template); err == nil { 435 | log.Infof("Check on your new Resource Group in the Azure Portal: %s\nYour site will be available shortly at: https://%s.azurewebsites.net\n", pLink, siteName) 436 | } else { 437 | log.Warnf("unable to poll for completion progress, your assets may or may not have finished provisioning.\nCheck on their status in the portal: %s\nError: %v\n", pLink, err) 438 | errOut <- err 439 | return 440 | } 441 | log.Info("finished deployment") 442 | }(deploymentResults) 443 | } 444 | 445 | doCache := func(ctx context.Context, errOut chan<- error, contents interface{}, location, flavor string) { 446 | defer close(errOut) 447 | log.Info("caching ", flavor) 448 | err := cache(ctx, contents, location) 449 | if err != nil { 450 | log.Errorf("unable to cache file %s because: %v", location, err) 451 | errOut <- err 452 | return 453 | } 454 | log.Debugf("%s cached", flavor) 455 | } 456 | 457 | templateSaveResults, parameterSaveResults := make(chan error), make(chan error) 458 | if provisionConfig.GetBool(SkipTemplateCacheName) { 459 | close(templateSaveResults) 460 | } else { 461 | go doCache(ctx, templateSaveResults, template.Template, TemplateDefault, "template") 462 | } 463 | 464 | if provisionConfig.GetBool(SkipParameterCacheName) { 465 | close(parameterSaveResults) 466 | } else { 467 | go doCache(ctx, parameterSaveResults, stripPasswords(deployParams), TemplateParametersDefault, "parameters") 468 | } 469 | 470 | waitOnResults := func(ctx context.Context, results <-chan error) error { 471 | select { 472 | case <-ctx.Done(): 473 | return ctx.Err() 474 | case err := <-results: 475 | return err 476 | } 477 | } 478 | 479 | waitOnResults(ctx, templateSaveResults) 480 | waitOnResults(ctx, parameterSaveResults) 481 | waitOnResults(ctx, deploymentResults) 482 | }, 483 | Args: func(cmd *cobra.Command, args []string) error { 484 | if provisionConfig.GetString(SubscriptionName) == "" { 485 | return fmt.Errorf("no value found for %q", SubscriptionName) 486 | } 487 | 488 | hasClientID := provisionConfig.GetString(ClientIDName) != "" 489 | hasClientSecret := provisionConfig.GetString(ClientSecretName) != "" 490 | 491 | if !hasClientSecret && !hasClientID { 492 | provisionConfig.Set(DeviceAuthName, true) 493 | } else if (hasClientID || hasClientSecret) && !(hasClientID && hasClientSecret) { 494 | return errors.New("--client-id and --client-secret must be specified together or not at all") 495 | } 496 | 497 | if rootConfig.GetBool(VerboseName) { 498 | rootConfig.Set(logOutputLevelName, logOutputLevelDebug) 499 | } 500 | 501 | if rawLevel := rootConfig.GetString(logOutputLevelName); rawLevel != "" { 502 | var level logrus.Level 503 | switch rawLevel { 504 | case logOutputLevelDebug: 505 | level = logrus.DebugLevel 506 | case logOutputLevelInfo: 507 | level = logrus.InfoLevel 508 | case logOutputLevelWarn: 509 | level = logrus.WarnLevel 510 | case logOutputLevelError: 511 | level = logrus.ErrorLevel 512 | case logOutputLevelFatal: 513 | level = logrus.FatalLevel 514 | case logOutputLevelPanic: 515 | level = logrus.PanicLevel 516 | default: 517 | return fmt.Errorf("unrecognized "+logOutputLevelName+": %s", rawLevel) 518 | } 519 | 520 | log.SetLevel(level) 521 | } 522 | 523 | if provisionConfig.GetString(TemplateName) == TemplateDefault { 524 | provisionConfig.Set(SkipTemplateCacheName, true) 525 | } 526 | 527 | if provisionConfig.GetString(LocationName) == LocationDefaultText { 528 | provisionConfig.SetDefault(LocationName, LocationDefault) 529 | } 530 | 531 | paramFile := provisionConfig.GetString(TemplateParametersName) 532 | p, err := loadFromParameterFile(paramFile) 533 | if err == nil { 534 | setDefaults(provisionConfig, p) 535 | deployParams = p 536 | } else if paramFile != TemplateParametersDefault { 537 | return fmt.Errorf("unable to load parameters file: %v", err) 538 | } 539 | 540 | nameGenerator := randname.Prefixed{ 541 | Prefix: siteDefaultPrefix + "-", 542 | Len: 10, 543 | Acceptable: append(randname.LowercaseAlphabet, randname.ArabicNumerals...), 544 | } 545 | 546 | if sn := provisionConfig.GetString(SiteName); sn == "" || sn == siteDefaultMessage { 547 | provisionConfig.Set(SiteName, nameGenerator.Generate()) 548 | } 549 | 550 | if rgn := provisionConfig.GetString(ResoureGroupName); rgn == "" || rgn == ResourceGroupDefault { 551 | provisionConfig.Set(ResoureGroupName, provisionConfig.GetString(SiteName)) 552 | } 553 | 554 | environment, err = azure.EnvironmentFromName(provisionConfig.GetString(EnvironmentName)) 555 | if err != nil { 556 | return err 557 | } 558 | 559 | return nil 560 | }, 561 | } 562 | 563 | func stripPasswords(original *DeploymentParameters) (copy *DeploymentParameters) { 564 | copy = NewDeploymentParameters() 565 | copy.Parameters = make(map[string]DeploymentParameter, len(original.Parameters)) 566 | copy.ContentVersion = original.ContentVersion 567 | copy.Schema = original.Schema 568 | 569 | for k, v := range original.Parameters { 570 | copy.Parameters[k] = v 571 | } 572 | delete(copy.Parameters, "databaseAdministratorLoginPassword") 573 | delete(copy.Parameters, "dockerRegistryServerPassword") 574 | return copy 575 | } 576 | 577 | func cache(ctx context.Context, contents interface{}, outputName string) error { 578 | if handle, err := os.Create(outputName); err == nil { 579 | defer handle.Close() 580 | 581 | enc := json.NewEncoder(handle) 582 | enc.SetIndent("", " ") 583 | err = enc.Encode(contents) 584 | if err != nil { 585 | return err 586 | } 587 | } else { 588 | return err 589 | } 590 | return nil 591 | } 592 | 593 | func portalLink(subscriptionID, rgName string) string { 594 | return fmt.Sprintf("https://portal.azure.com/#resource/subscriptions/%s/resourceGroups/%s/overview", subscriptionID, rgName) 595 | } 596 | 597 | // insertResourceGroup checks for a Resource Groups's existence, if it is not found it creates that resource group. If 598 | // that resource group exists, it leaves it alone. 599 | func insertResourceGroup(ctx context.Context, groups resources.GroupsClient, name string, location string) (bool, error) { 600 | existenceResp, err := groups.CheckExistence(ctx, name) 601 | if err != nil { 602 | return false, err 603 | } 604 | 605 | switch existenceResp.StatusCode { 606 | case http.StatusNoContent: 607 | return false, nil 608 | case http.StatusNotFound: 609 | createResp, err := groups.CreateOrUpdate(ctx, name, resources.Group{ 610 | Location: &location, 611 | }) 612 | if err != nil { 613 | return false, err 614 | } 615 | 616 | if createResp.StatusCode == http.StatusCreated { 617 | return true, nil 618 | } else if createResp.StatusCode == http.StatusOK { 619 | return false, nil 620 | } else { 621 | return false, fmt.Errorf("unexpected status code %d during resource group creation", createResp.StatusCode) 622 | } 623 | default: 624 | return false, fmt.Errorf("unexpected status code %d during resource group existence check", existenceResp.StatusCode) 625 | } 626 | } 627 | 628 | func getAuthorizer(ctx context.Context, subscriptionID, clientID, clientSecret, tenantID string) (autorest.Authorizer, error) { 629 | const commonTenant = "common" 630 | 631 | if tenantID == "" { 632 | tenantID = commonTenant 633 | } 634 | 635 | config, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, tenantID) 636 | if err != nil { 637 | return nil, err 638 | } 639 | 640 | if provisionConfig.GetBool(DeviceAuthName) { 641 | var intermediate *adal.Token 642 | 643 | client := &http.Client{} 644 | code, err := adal.InitiateDeviceAuth( 645 | client, 646 | *config, 647 | deviceClientID, 648 | environment.ResourceManagerEndpoint) 649 | if err != nil { 650 | return nil, err 651 | } 652 | fmt.Println(*code.Message) 653 | token, err := adal.WaitForUserCompletion(client, code) 654 | if err != nil { 655 | return nil, err 656 | } 657 | intermediate = token 658 | 659 | if tenantID == commonTenant { 660 | var final autorest.Authorizer 661 | tenantID, final, err = getTenant(ctx, intermediate, subscriptionID) 662 | if err != nil { 663 | return nil, err 664 | } 665 | 666 | return final, nil 667 | } 668 | return autorest.NewBearerAuthorizer(intermediate), nil 669 | } 670 | 671 | if tenantID == commonTenant { 672 | return nil, errors.New("tenant inference unsupported with Service Principal authentication") 673 | } 674 | 675 | auth, err := adal.NewServicePrincipalToken( 676 | *config, 677 | clientID, 678 | clientSecret, 679 | environment.ResourceManagerEndpoint) 680 | if err != nil { 681 | return nil, err 682 | } 683 | log.WithFields(logrus.Fields{"client": clientID}).Debug("service principal token created") 684 | return autorest.NewBearerAuthorizer(auth), nil 685 | } 686 | 687 | func getDatabaseFlavor(buffaloRoot, profile string) (string, string, error) { 688 | app := meta.New(buffaloRoot) 689 | if !app.WithPop { 690 | return "none", "", nil 691 | } 692 | 693 | conn, err := pop.Connect(profile) 694 | if err != nil { 695 | return "", "", err 696 | } 697 | 698 | return conn.Dialect.Name(), conn.Dialect.Details().Database, nil 699 | } 700 | 701 | func doDeployment(ctx context.Context, authorizer autorest.Authorizer, subscriptionID, resourceGroup string, properties *resources.DeploymentProperties) (err error) { 702 | deployments := resources.NewDeploymentsClient(subscriptionID) 703 | deployments.Authorizer = authorizer 704 | deployments.AddToUserAgent(userAgent) 705 | 706 | fut, err := deployments.CreateOrUpdate(ctx, resourceGroup, siteDefaultPrefix, resources.Deployment{Properties: properties}) 707 | if err != nil { 708 | return 709 | } 710 | 711 | err = fut.WaitForCompletion(ctx, deployments.Client) 712 | return 713 | } 714 | 715 | func getDeploymentTemplate(ctx context.Context, raw string) (*resources.DeploymentProperties, error) { 716 | if isSupportedLink(raw) { 717 | buf := bytes.NewBuffer([]byte{}) 718 | 719 | err := downloadTemplate(ctx, buf, raw) 720 | if err != nil { 721 | return nil, err 722 | } 723 | 724 | return &resources.DeploymentProperties{ 725 | Template: json.RawMessage(buf.Bytes()), 726 | }, nil 727 | } 728 | 729 | handle, err := os.Open(raw) 730 | if err != nil { 731 | return nil, err 732 | } 733 | 734 | contents, err := ioutil.ReadAll(handle) 735 | if err != nil { 736 | return nil, err 737 | } 738 | 739 | return &resources.DeploymentProperties{ 740 | Template: json.RawMessage(contents), 741 | }, nil 742 | } 743 | 744 | var redirectCodes = map[int]struct{}{ 745 | http.StatusMovedPermanently: {}, 746 | http.StatusPermanentRedirect: {}, 747 | http.StatusTemporaryRedirect: {}, 748 | http.StatusSeeOther: {}, 749 | http.StatusFound: {}, 750 | } 751 | 752 | var temporaryFailureCodes = map[int]struct{}{ 753 | http.StatusTooManyRequests: {}, 754 | http.StatusGatewayTimeout: {}, 755 | http.StatusRequestTimeout: {}, 756 | } 757 | 758 | var acceptedCodes = map[int]struct{}{ 759 | http.StatusOK: {}, 760 | } 761 | 762 | func downloadTemplate(ctx context.Context, dest io.Writer, src string) error { 763 | const maxRedirects = 5 764 | const maxRetries = 3 765 | var download func(context.Context, io.Writer, string, uint) error 766 | 767 | log.Debug("downloading template: ", src) 768 | 769 | download = func(ctx context.Context, dest io.Writer, src string, depth uint) (err error) { 770 | if depth > maxRedirects { 771 | return errors.New("too many redirects") 772 | } 773 | 774 | for attempt := 0; attempt < maxRetries; attempt++ { 775 | var req *http.Request 776 | var resp *http.Response 777 | 778 | req, err = http.NewRequest(http.MethodGet, src, nil) 779 | if err != nil { 780 | return 781 | } 782 | req = req.WithContext(ctx) 783 | 784 | resp, err = http.DefaultClient.Do(req) 785 | if err != nil { 786 | return 787 | } 788 | 789 | if _, ok := acceptedCodes[resp.StatusCode]; ok { 790 | _, err = io.Copy(dest, resp.Body) 791 | return 792 | } 793 | 794 | statusCodeLogger := log.WithFields(logrus.Fields{"status-code": resp.StatusCode}) 795 | 796 | if _, ok := redirectCodes[resp.StatusCode]; ok { 797 | loc := resp.Header.Get("Location") 798 | statusCodeLogger.WithFields(logrus.Fields{"location": loc}).Debug("following HTTP redirect") 799 | return download(ctx, dest, loc, depth+1) 800 | } 801 | 802 | if _, ok := temporaryFailureCodes[resp.StatusCode]; ok { 803 | statusCodeLogger.Debug("recoverable HTTP failure, retrying.") 804 | continue 805 | } 806 | 807 | err = fmt.Errorf("unexpected status code: %d", resp.StatusCode) 808 | return 809 | } 810 | 811 | err = errors.New("too many attempts") 812 | return 813 | } 814 | 815 | return download(ctx, dest, src, 1) 816 | } 817 | 818 | func getTenant(ctx context.Context, common *adal.Token, subscription string) (string, autorest.Authorizer, error) { 819 | tenants := subscriptions.NewTenantsClient() 820 | tenants.Authorizer = autorest.NewBearerAuthorizer(common) 821 | 822 | var err error 823 | var tenantList subscriptions.TenantListResultIterator 824 | 825 | subscriptionClient := subscriptions.NewClient() 826 | 827 | log.WithFields(logrus.Fields{"subscription": subscription}).Info("using authorization to infer tenant") 828 | 829 | for tenantList, err = tenants.ListComplete(ctx); err == nil && tenantList.NotDone(); err = tenantList.Next() { 830 | var subscriptionList subscriptions.ListResultIterator 831 | currentTenant := *tenantList.Value().TenantID 832 | currentConfig, err := adal.NewOAuthConfig(environment.ActiveDirectoryEndpoint, currentTenant) 833 | if err != nil { 834 | return "", nil, err 835 | } 836 | currentAuth, err := adal.NewServicePrincipalTokenFromManualToken(*currentConfig, deviceClientID, environment.ResourceManagerEndpoint, adal.Token{ 837 | RefreshToken: common.RefreshToken, 838 | }) 839 | if err != nil { 840 | return "", nil, err 841 | } 842 | subscriptionClient.Authorizer = autorest.NewBearerAuthorizer(currentAuth) 843 | 844 | for subscriptionList, err = subscriptionClient.ListComplete(ctx); err == nil && subscriptionList.NotDone(); err = subscriptionList.Next() { 845 | if currentSub := subscriptionList.Value(); currentSub.SubscriptionID != nil && strings.EqualFold(*currentSub.SubscriptionID, subscription) { 846 | provisionConfig.Set(TenantIDName, *tenantList.Value().TenantID) 847 | return *tenantList.Value().TenantID, subscriptionClient.Authorizer, nil 848 | } 849 | } 850 | } 851 | if err != nil { 852 | return "", nil, err 853 | } 854 | 855 | return "", nil, fmt.Errorf("unable to find subscription: %s", subscription) 856 | } 857 | 858 | var normalizeScheme = strings.ToLower 859 | var supportedLinkSchemes = map[string]struct{}{ 860 | normalizeScheme("http"): {}, 861 | normalizeScheme("https"): {}, 862 | } 863 | 864 | // isSupportedLink interrogates a string to decide if it is a RequestURI that is supported by the Azure template engine 865 | // as defined here: 866 | // https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-linked-templates#external-template-and-external-parameters 867 | func isSupportedLink(subject string) bool { 868 | parsed, err := url.ParseRequestURI(subject) 869 | if err != nil { 870 | return false 871 | } 872 | 873 | _, ok := supportedLinkSchemes[normalizeScheme(parsed.Scheme)] 874 | 875 | return ok 876 | } 877 | 878 | func setDefaults(conf *viper.Viper, params *DeploymentParameters) { 879 | if name, ok := params.Parameters["name"]; ok { 880 | conf.SetDefault(SiteName, name.Value) 881 | } 882 | 883 | if database, ok := params.Parameters["database"]; ok { 884 | conf.SetDefault(DatabaseTypeName, database.Value) 885 | } 886 | 887 | if databaseName, ok := params.Parameters["databaseName"]; ok { 888 | conf.SetDefault(DatabaseNameName, databaseName.Value) 889 | } 890 | 891 | if image, ok := params.Parameters["imageName"]; ok { 892 | conf.SetDefault(ImageName, image.Value) 893 | } 894 | 895 | if dbAdmin, ok := params.Parameters["databaseAdministratorLogin"]; ok { 896 | conf.SetDefault(DatabaseAdminName, dbAdmin.Value) 897 | } 898 | 899 | if dockerAccess, ok := params.Parameters["dockerRegistryAccess"]; ok { 900 | conf.SetDefault(DockerRegistryAccessName, dockerAccess.Value) 901 | } 902 | 903 | if dockerURL, ok := params.Parameters["dockerRegistryServerURL"]; ok { 904 | conf.SetDefault(DockerRegistryURLName, dockerURL.Value) 905 | } 906 | 907 | if dockerUsername, ok := params.Parameters["dockerRegistryServerUsername"]; ok { 908 | conf.SetDefault(DockerRegistryUsernameName, dockerUsername.Value) 909 | } 910 | } 911 | 912 | func loadFromParameterFile(paramFile string) (*DeploymentParameters, error) { 913 | loaded := NewDeploymentParameters() 914 | if _, err := os.Stat(TemplateParametersDefault); err == nil { 915 | provisionConfig.SetDefault(TemplateParametersName, TemplateParametersDefault) 916 | if handle, err := os.Open(provisionConfig.GetString(TemplateParametersName)); err == nil { 917 | dec := json.NewDecoder(handle) 918 | err = dec.Decode(loaded) 919 | if err != nil { 920 | return nil, err 921 | } 922 | } 923 | } else { 924 | return nil, err 925 | } 926 | return loaded, nil 927 | } 928 | 929 | func init() { 930 | const redactedMessage = "[redacted]" 931 | azureCmd.AddCommand(provisionCmd) 932 | 933 | godotenv.Load() 934 | 935 | // Here you will define your flags and configuration settings. 936 | 937 | // Cobra supports Persistent Flags which will work for this command 938 | // and all subcommands, e.g.: 939 | // provisionCmd.PersistentFlags().String("foo", "", "A help for foo") 940 | 941 | // Cobra supports local flags which will only run when this command 942 | // is called directly, e.g.: 943 | // provisionCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") 944 | 945 | provisionConfig.BindEnv(SubscriptionName, "AZURE_SUBSCRIPTION_ID", "AZ_SUBSCRIPTION_ID") 946 | provisionConfig.BindEnv(ClientIDName, "AZURE_CLIENT_ID", "AZ_CLIENT_ID") 947 | provisionConfig.BindEnv(ClientSecretName, "AZURE_CLIENT_SECRET", "AZ_CLIENT_SECRET") 948 | provisionConfig.BindEnv(TenantIDName, "AZURE_TENANT_ID", "AZ_TENANT_ID") 949 | provisionConfig.BindEnv(EnvironmentName, "AZURE_ENVIRONMENT", "AZ_ENVIRONMENT") 950 | provisionConfig.BindEnv(ProfileName, "GO_ENV") 951 | provisionConfig.BindEnv(DatabasePasswordName, DatabasePasswordEnvVar, "BUFFALO_AZURE_DB_PASSWORD", "BUFFALO_AZ_DATABASE_PASSWORD", "BUFFALO_AZ_DB_PASSWORD") 952 | provisionConfig.BindEnv(DockerRegistryPasswordName, DockerRegistryPasswordEnvVar) 953 | 954 | provisionConfig.SetDefault(ProfileName, "development") 955 | 956 | if dialect, dbname, err := getDatabaseFlavor(".", provisionConfig.GetString(ProfileName)); err == nil { 957 | provisionConfig.SetDefault(DatabaseTypeName, dialect) 958 | provisionConfig.SetDefault(DatabaseNameName, dbname) 959 | } else { 960 | provisionConfig.SetDefault(DatabaseTypeName, "none") 961 | log.WithFields(logrus.Fields{"error": err}).Warn("unable to parse buffalo application for db dialect.") 962 | } 963 | 964 | provisionConfig.SetDefault(DatabaseAdminName, DatabaseAdminDefault) 965 | provisionConfig.SetDefault(ImageName, ImageDefault) 966 | provisionConfig.SetDefault(EnvironmentName, EnvironmentDefault) 967 | provisionConfig.SetDefault(ResoureGroupName, ResourceGroupDefault) 968 | provisionConfig.SetDefault(LocationName, LocationDefaultText) 969 | provisionConfig.SetDefault(SiteName, siteDefaultMessage) 970 | provisionConfig.SetDefault(TemplateParametersName, TemplateParametersDefault) 971 | provisionConfig.SetDefault(DockerRegistryAccessName, DockerRegistryAccessDefault) 972 | 973 | var sanitizedClientSecret string 974 | if rawSecret := provisionConfig.GetString(ClientSecretName); rawSecret != "" { 975 | const safeCharCount = 10 976 | if len(rawSecret) > safeCharCount { 977 | sanitizedClientSecret = fmt.Sprintf("...%s", rawSecret[len(rawSecret)-safeCharCount:]) 978 | } else { 979 | sanitizedClientSecret = redactedMessage 980 | } 981 | } 982 | 983 | if _, err := os.Stat(TemplateDefault); err == nil { 984 | provisionConfig.SetDefault(TemplateName, TemplateDefault) 985 | } else { 986 | provisionConfig.SetDefault(TemplateName, TemplateDefaultLink) 987 | } 988 | 989 | if p, err := loadFromParameterFile(provisionConfig.GetString(TemplateParametersName)); err == nil { 990 | setDefaults(provisionConfig, p) 991 | deployParams = p 992 | } else { 993 | deployParams = NewDeploymentParameters() 994 | } 995 | 996 | dbPassText := provisionConfig.GetString(DatabasePasswordName) 997 | if dbPassText == "" { 998 | dbPassText = DatabasePasswordDefault 999 | provisionConfig.SetDefault(DatabasePasswordName, DatabasePasswordDefault) 1000 | } else { 1001 | dbPassText = redactedMessage 1002 | } 1003 | 1004 | dockerPassText := provisionConfig.GetString(DockerRegistryPasswordName) 1005 | if dockerPassText != "" { 1006 | dockerPassText = redactedMessage 1007 | } 1008 | 1009 | provisionCmd.Flags().StringP(ImageName, ImageShorthand, provisionConfig.GetString(ImageName), imageUsage) 1010 | provisionCmd.Flags().StringP(TemplateName, TemplateShorthand, provisionConfig.GetString(TemplateName), templateUsage) 1011 | provisionCmd.Flags().StringP(SubscriptionName, SubscriptionShorthand, provisionConfig.GetString(SubscriptionName), subscriptionUsage) 1012 | provisionCmd.Flags().String(ClientIDName, provisionConfig.GetString(ClientIDName), clientIDUsage) 1013 | provisionCmd.Flags().String(ClientSecretName, sanitizedClientSecret, clientSecretUsage) 1014 | provisionCmd.Flags().Bool(DeviceAuthName, false, deviceAuthUsage) 1015 | provisionCmd.Flags().String(TenantIDName, provisionConfig.GetString(TenantIDName), tenantUsage) 1016 | provisionCmd.Flags().StringP(EnvironmentName, EnvironmentShorthand, provisionConfig.GetString(EnvironmentName), environmentUsage) 1017 | provisionCmd.Flags().String(DatabaseNameName, provisionConfig.GetString(DatabaseNameName), databaseNameUsage) 1018 | provisionCmd.Flags().StringP(DatabaseTypeName, DatabaseShorthand, provisionConfig.GetString(DatabaseTypeName), databaseUsage) 1019 | provisionCmd.Flags().StringP(ResoureGroupName, ResourceGroupShorthand, provisionConfig.GetString(ResoureGroupName), resourceGroupUsage) 1020 | provisionCmd.Flags().StringP(SiteName, SiteShorthand, provisionConfig.GetString(SiteName), siteUsage) 1021 | provisionCmd.Flags().StringP(LocationName, LocationShorthand, provisionConfig.GetString(LocationName), locationUsage) 1022 | provisionCmd.Flags().BoolP(SkipTemplateCacheName, SkipTemplateCacheShorthand, false, skipTemplateCacheUsage) 1023 | provisionCmd.Flags().BoolP(SkipParameterCacheName, SkipParameterCacheShorthand, false, skipParameterCacheUsage) 1024 | provisionCmd.Flags().BoolP(SkipDeploymentName, SkipDeploymentShorthand, false, skipDeploymentUsage) 1025 | provisionCmd.Flags().StringP(DatabasePasswordName, DatabasePasswordShorthand, dbPassText, databasePasswordUsage) 1026 | provisionCmd.Flags().String(DatabaseAdminName, provisionConfig.GetString(DatabaseAdminName), databaseAdminUsage) 1027 | provisionCmd.Flags().StringP(TemplateParametersName, TemplateParametersShorthand, provisionConfig.GetString(TemplateParametersName), templateParametersUsage) 1028 | provisionCmd.Flags().String(DockerRegistryAccessName, provisionConfig.GetString(DockerRegistryAccessName), dockerRegistryAccessUsage) 1029 | provisionCmd.Flags().String(DockerRegistryURLName, provisionConfig.GetString(DockerRegistryURLName), dockerRegistryURLUsage) 1030 | provisionCmd.Flags().String(DockerRegistryUsernameName, provisionConfig.GetString(DockerRegistryUsernameName), dockerRegistryUsernameUsage) 1031 | provisionCmd.Flags().String(DockerRegistryPasswordName, dockerPassText, dockerRegistryPasswordUsage) 1032 | 1033 | provisionConfig.BindPFlags(provisionCmd.Flags()) 1034 | 1035 | userAgentBuilder := bytes.NewBufferString("buffalo-azure") 1036 | if version != "" { 1037 | userAgentBuilder.WriteRune('/') 1038 | userAgentBuilder.WriteString(version) 1039 | } 1040 | userAgent = userAgentBuilder.String() 1041 | } 1042 | --------------------------------------------------------------------------------