diff --git a/.golangci.yml b/.golangci.yml index 5f716b517..4d5e63df1 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,35 +1,65 @@ -service: - golangci-lint-version: 1.23.0 - run: timeout: 5m + modules-download-mode: readonly linters-settings: + goconst: + min-len: 2 + min-occurrences: 2 gofmt: simplify: true + goimports: + local-prefixes: github.com/mattermost/mattermost-plugin-github + golint: + min-confidence: 0 govet: check-shadowing: true enable-all: true + misspell: + locale: US linters: disable-all: true enable: + - bodyclose - deadcode + - errcheck + - goconst + - gocritic - gofmt + - goimports + - golint + - gosec - gosimple - govet - ineffassign + - interfacer + - misspell + - nakedret + - staticcheck - structcheck + - stylecheck + - typecheck - unconvert - unused - varcheck - # TODO: enable this later - # - errcheck + - whitespace issues: exclude-rules: - - linters: - # ignore unused warning for manifest - - varcheck - - deadcode - text: "manifest" + - path: server/manifest.go + linters: + - deadcode + - unused + - varcheck + - path: server/configuration.go + linters: + - unused + - path: _test\.go + linters: + - bodyclose + - goconst + - scopelint # https://github.com/kyoh86/scopelint/issues/4 + - path: server/webhook.go + linters: + - goconst diff --git a/Makefile b/Makefile index 8f2bd607e..ca7f0bf9f 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,9 @@ GO ?= $(shell command -v go 2> /dev/null) NPM ?= $(shell command -v npm 2> /dev/null) CURL ?= $(shell command -v curl 2> /dev/null) MANIFEST_FILE ?= plugin.json +GOPATH ?= $(shell go env GOPATH) +GO_TEST_FLAGS ?= -race +GO_BUILD_FLAGS ?= MM_UTILITIES_DIR ?= ../mattermost-utilities export GO111MODULE=on @@ -14,6 +17,11 @@ include build/setup.mk BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz +# Include custom makefile, if present +ifneq ($(wildcard build/custom.mk),) + include build/custom.mk +endif + ## Checks the code style, tests, builds and bundles the plugin. all: check-style test dist @@ -22,7 +30,7 @@ all: check-style test dist apply: ./build/bin/manifest apply -## Runs golangci-lint against all packages. +## Runs golangci-lint and eslint. .PHONY: check-style check-style: webapp/.npminstall golangci-lint @echo Checking for style guide compliance @@ -31,8 +39,9 @@ ifneq ($(HAS_WEBAPP),) cd webapp && npm run lint endif -golangci-lint: ## Run golangci-lint on codebase -# https://stackoverflow.com/a/677212/1027058 (check if a command exists or not) +## Run golangci-lint on codebase. +.PHONY: golangci-lint +golangci-lint: @if ! [ -x "$$(command -v golangci-lint)" ]; then \ echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \ exit 1; \ @@ -46,9 +55,9 @@ golangci-lint: ## Run golangci-lint on codebase server: ifneq ($(HAS_SERVER),) mkdir -p server/dist; - cd server && env GOOS=linux GOARCH=amd64 $(GO) build -o dist/plugin-linux-amd64; - cd server && env GOOS=darwin GOARCH=amd64 $(GO) build -o dist/plugin-darwin-amd64; - cd server && env GOOS=windows GOARCH=amd64 $(GO) build -o dist/plugin-windows-amd64.exe; + cd server && env GOOS=linux GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-linux-amd64; + cd server && env GOOS=darwin GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-darwin-amd64; + cd server && env GOOS=windows GOARCH=amd64 $(GO) build $(GO_BUILD_FLAGS) -o dist/plugin-windows-amd64.exe; endif ## Ensures NPM dependencies are installed without having to run this all the time. @@ -65,6 +74,14 @@ ifneq ($(HAS_WEBAPP),) cd webapp && $(NPM) run build; endif +## Builds the webapp in debug mode, if it exists. +.PHONY: webapp-debug +webapp-debug: webapp/.npminstall +ifneq ($(HAS_WEBAPP),) + cd webapp && \ + $(NPM) run debug; +endif + ## Generates a tar bundle of the plugin for install. .PHONY: bundle bundle: @@ -94,39 +111,33 @@ endif dist: apply server webapp bundle ## Installs the plugin to a (development) server. +## It uses the API if appropriate environment variables are defined, +## and otherwise falls back to trying to copy the plugin to a sibling mattermost-server directory. .PHONY: deploy deploy: dist -## It uses the API if appropriate environment variables are defined, -## or copying the files directly to a sibling mattermost-server directory. -ifneq ($(and $(MM_SERVICESETTINGS_SITEURL),$(MM_ADMIN_USERNAME),$(MM_ADMIN_PASSWORD),$(CURL)),) - @echo "Installing plugin via API" - $(eval TOKEN := $(shell curl -i -X POST $(MM_SERVICESETTINGS_SITEURL)/api/v4/users/login -d '{"login_id": "$(MM_ADMIN_USERNAME)", "password": "$(MM_ADMIN_PASSWORD)"}' | grep Token | cut -f2 -d' ' 2> /dev/null)) - @curl -s -H "Authorization: Bearer $(TOKEN)" -X POST $(MM_SERVICESETTINGS_SITEURL)/api/v4/plugins -F "plugin=@dist/$(BUNDLE_NAME)" -F "force=true" > /dev/null && \ - curl -s -H "Authorization: Bearer $(TOKEN)" -X POST $(MM_SERVICESETTINGS_SITEURL)/api/v4/plugins/$(PLUGIN_ID)/enable > /dev/null && \ - echo "OK." || echo "Sorry, something went wrong." -else ifneq ($(wildcard ../mattermost-server/.*),) - @echo "Installing plugin via filesystem. Server restart and manual plugin enabling required" - mkdir -p ../mattermost-server/plugins - tar -C ../mattermost-server/plugins -zxvf dist/$(BUNDLE_NAME) -else - @echo "No supported deployment method available. Install plugin manually." -endif + ./build/bin/deploy $(PLUGIN_ID) dist/$(BUNDLE_NAME) + +.PHONY: debug-deploy +debug-deploy: debug-dist deploy + +.PHONY: debug-dist +debug-dist: apply server webapp-debug bundle ## Runs any lints and unit tests defined for the server and webapp, if they exist. .PHONY: test test: webapp/.npminstall ifneq ($(HAS_SERVER),) - $(GO) test -race -v ./server/... + $(GO) test -v $(GO_TEST_FLAGS) ./server/... endif ifneq ($(HAS_WEBAPP),) - cd webapp && $(NPM) run fix; + cd webapp && $(NPM) run fix && $(NPM) run test; endif ## Creates a coverage report for the server code. .PHONY: coverage -coverage: +coverage: webapp/.npminstall ifneq ($(HAS_SERVER),) - $(GO) test -race -coverprofile=server/coverage.txt ./server/... + $(GO) test $(GO_TEST_FLAGS) -coverprofile=server/coverage.txt ./server/... $(GO) tool cover -html=server/coverage.txt endif @@ -134,8 +145,11 @@ endif .PHONY: i18n-extract i18n-extract: ifneq ($(HAS_WEBAPP),) - @[[ -d $(MM_UTILITIES_DIR) ]] || echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command" - @[[ -d $(MM_UTILITIES_DIR) ]] && cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir ../mattermost-plugin-demo/webapp +ifeq ($(HAS_MM_UTILITIES),) + @echo "You must clone github.com/mattermost/mattermost-utilities repo in .. to use this command" +else + cd $(MM_UTILITIES_DIR) && npm install && npm run babel && node mmjstool/build/index.js i18n extract-webapp --webapp-dir $(PWD)/webapp +endif endif ## Clean removes all build artifacts. @@ -143,15 +157,17 @@ endif clean: rm -fr dist/ ifneq ($(HAS_SERVER),) + rm -fr server/coverage.txt rm -fr server/dist endif ifneq ($(HAS_WEBAPP),) rm -fr webapp/.npminstall + rm -fr webapp/junit.xml rm -fr webapp/dist rm -fr webapp/node_modules endif rm -fr build/bin/ -# Help documentatin à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html +# Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html help: @cat Makefile | grep -v '\.PHONY' | grep -v '\help:' | grep -B1 -E '^[a-zA-Z0-9_.-]+:.*' | sed -e "s/:.*//" | sed -e "s/^## //" | grep -v '\-\-' | sed '1!G;h;$$!d' | awk 'NR%2{printf "\033[36m%-30s\033[0m",$$0;next;}1' | sort diff --git a/build/custom.mk b/build/custom.mk new file mode 100644 index 000000000..efad4fbd8 --- /dev/null +++ b/build/custom.mk @@ -0,0 +1 @@ +# Include custome targets and environment variables here diff --git a/build/deploy/main.go b/build/deploy/main.go new file mode 100644 index 000000000..186bb4922 --- /dev/null +++ b/build/deploy/main.go @@ -0,0 +1,122 @@ +// main handles deployment of the plugin to a development server using either the Client4 API +// or by copying the plugin bundle into a sibling mattermost-server/plugin directory. +package main + +import ( + "fmt" + "log" + "os" + "path/filepath" + + "github.com/mattermost/mattermost-server/v5/model" + "github.com/mholt/archiver/v3" + "github.com/pkg/errors" +) + +func main() { + err := deploy() + if err != nil { + fmt.Printf("Failed to deploy: %s\n", err.Error()) + fmt.Println() + fmt.Println("Usage:") + fmt.Println(" deploy ") + os.Exit(1) + } +} + +// deploy handles deployment of the plugin to a development server. +func deploy() error { + if len(os.Args) < 3 { + return errors.New("invalid number of arguments") + } + + pluginID := os.Args[1] + bundlePath := os.Args[2] + + siteURL := os.Getenv("MM_SERVICESETTINGS_SITEURL") + adminToken := os.Getenv("MM_ADMIN_TOKEN") + adminUsername := os.Getenv("MM_ADMIN_USERNAME") + adminPassword := os.Getenv("MM_ADMIN_PASSWORD") + copyTargetDirectory, _ := filepath.Abs("../mattermost-server") + + if siteURL != "" { + client := model.NewAPIv4Client(siteURL) + + if adminToken != "" { + log.Printf("Authenticating using token against %s.", siteURL) + client.SetToken(adminToken) + + return uploadPlugin(client, pluginID, bundlePath) + } + + if adminUsername != "" && adminPassword != "" { + client := model.NewAPIv4Client(siteURL) + log.Printf("Authenticating as %s against %s.", adminUsername, siteURL) + _, resp := client.Login(adminUsername, adminPassword) + if resp.Error != nil { + return errors.Wrapf(resp.Error, "failed to login as %s", adminUsername) + } + + return uploadPlugin(client, pluginID, bundlePath) + } + } + + _, err := os.Stat(copyTargetDirectory) + if os.IsNotExist(err) { + return errors.New("no supported deployment method available, please install plugin manually") + } else if err != nil { + return errors.Wrapf(err, "failed to stat %s", copyTargetDirectory) + } + + log.Printf("Installing plugin to mattermost-server found in %s.", copyTargetDirectory) + log.Print("Server restart required to load updated plugin.") + return copyPlugin(pluginID, copyTargetDirectory, bundlePath) +} + +// uploadPlugin attempts to upload and enable a plugin via the Client4 API. +// It will fail if plugin uploads are disabled. +func uploadPlugin(client *model.Client4, pluginID, bundlePath string) error { + pluginBundle, err := os.Open(bundlePath) + if err != nil { + return errors.Wrapf(err, "failed to open %s", bundlePath) + } + defer pluginBundle.Close() + + log.Print("Uploading plugin via API.") + _, resp := client.UploadPluginForced(pluginBundle) + if resp.Error != nil { + return errors.Wrap(resp.Error, "failed to upload plugin bundle") + } + + log.Print("Enabling plugin.") + _, resp = client.EnablePlugin(pluginID) + if resp.Error != nil { + return errors.Wrap(resp.Error, "Failed to enable plugin") + } + + return nil +} + +// copyPlugin attempts to install a plugin by copying it to a sibling ../mattermost-server/plugin +// directory. A server restart is required before the plugin will start. +func copyPlugin(pluginID, targetPath, bundlePath string) error { + targetPath = filepath.Join(targetPath, "plugins") + + err := os.MkdirAll(targetPath, 0777) + if err != nil { + return errors.Wrapf(err, "failed to create %s", targetPath) + } + + existingPluginPath := filepath.Join(targetPath, pluginID) + err = os.RemoveAll(existingPluginPath) + if err != nil { + return errors.Wrapf(err, "failed to remove existing existing plugin directory %s", existingPluginPath) + } + + err = archiver.Unarchive(bundlePath, targetPath) + if err != nil { + return errors.Wrapf(err, "failed to unarchive %s into %s", bundlePath, targetPath) + } + + return nil +} diff --git a/build/manifest/main.go b/build/manifest/main.go index 4bf4e19ad..89a397c1a 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -10,18 +10,22 @@ import ( "github.com/pkg/errors" ) -const pluginIdGoFileTemplate = `package main +const pluginIDGoFileTemplate = `// This file is automatically generated. Do not modify it manually. + +package main var manifest = struct { - Id string + ID string Version string }{ - Id: "%s", + ID: "%s", Version: "%s", } ` -const pluginIdJsFileTemplate = `export const id = '%s'; +const pluginIDJSFileTemplate = `// This file is automatically generated. Do not modify it manually. + +export const id = '%s'; export const version = '%s'; ` @@ -38,7 +42,7 @@ func main() { cmd := os.Args[1] switch cmd { case "id": - dumpPluginId(manifest) + dumpPluginID(manifest) case "version": dumpPluginVersion(manifest) @@ -87,7 +91,7 @@ func findManifest() (*model.Manifest, error) { } // dumpPluginId writes the plugin id from the given manifest to standard out -func dumpPluginId(manifest *model.Manifest) { +func dumpPluginID(manifest *model.Manifest) { fmt.Printf("%s", manifest.Id) } @@ -101,7 +105,7 @@ func applyManifest(manifest *model.Manifest) error { if manifest.HasServer() { if err := ioutil.WriteFile( "server/manifest.go", - []byte(fmt.Sprintf(pluginIdGoFileTemplate, manifest.Id, manifest.Version)), + []byte(fmt.Sprintf(pluginIDGoFileTemplate, manifest.Id, manifest.Version)), 0644, ); err != nil { return errors.Wrap(err, "failed to write server/manifest.go") @@ -111,7 +115,7 @@ func applyManifest(manifest *model.Manifest) error { if manifest.HasWebapp() { if err := ioutil.WriteFile( "webapp/src/manifest.js", - []byte(fmt.Sprintf(pluginIdJsFileTemplate, manifest.Id, manifest.Version)), + []byte(fmt.Sprintf(pluginIDJSFileTemplate, manifest.Id, manifest.Version)), 0644, ); err != nil { return errors.Wrap(err, "failed to open webapp/src/manifest.js") diff --git a/build/setup.mk b/build/setup.mk index 13ece2b8c..bc1fdc358 100644 --- a/build/setup.mk +++ b/build/setup.mk @@ -7,6 +7,9 @@ endif # Ensure that the build tools are compiled. Go's caching makes this quick. $(shell cd build/manifest && $(GO) build -o ../bin/manifest) +# Ensure that the deployment tools are compiled. Go's caching makes this quick. +$(shell cd build/deploy && $(GO) build -o ../bin/deploy) + # Extract the plugin id from the manifest. PLUGIN_ID ?= $(shell build/bin/manifest id) ifeq ($(PLUGIN_ID),) @@ -28,6 +31,12 @@ HAS_WEBAPP ?= $(shell build/bin/manifest has_webapp) # Determine if a /public folder is in use HAS_PUBLIC ?= $(wildcard public/.) +# Determine if the mattermost-utilities repo is present +HAS_MM_UTILITIES ?= $(wildcard $(MM_UTILITIES_DIR)/.) + +# Store the current path for later use +PWD ?= $(shell pwd) + # Ensure that npm (and thus node) is installed. ifneq ($(HAS_WEBAPP),) ifeq ($(NPM),) diff --git a/go.mod b/go.mod index 265c358f0..91f1baaa3 100644 --- a/go.mod +++ b/go.mod @@ -3,11 +3,12 @@ module github.com/mattermost/mattermost-plugin-github go 1.12 require ( - github.com/Masterminds/sprig/v3 v3.0.2 + github.com/Masterminds/sprig/v3 v3.1.0 github.com/google/go-github/v31 v31.0.0 github.com/gorilla/mux v1.7.4 - github.com/mattermost/mattermost-server/v5 v5.18.0 - github.com/pkg/errors v0.8.1 - github.com/stretchr/testify v1.4.0 - golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + github.com/mattermost/mattermost-server/v5 v5.23.0 + github.com/mholt/archiver/v3 v3.3.0 + github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.5.1 + golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d ) diff --git a/go.sum b/go.sum index 8770dd66e..4c4de453f 100644 --- a/go.sum +++ b/go.sum @@ -1,43 +1,52 @@ cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.31.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.37.0/go.mod h1:TS1dMSSfndXH133OKGwekG838Om/cQT0BUHV3HcBgoo= cloud.google.com/go v0.37.1/go.mod h1:SAbnLi6YTSPKSI0dTUEOVLCkyPfKXK8n4ibqiMoj4ok= contrib.go.opencensus.io/exporter/ocagent v0.4.9/go.mod h1:ueLzZcP7LPhPulEBukGn4aLh7Mx9YJwpVJ9nL2FYltw= +dmitri.shuralyov.com/app/changes v0.0.0-20180602232624-0a106ad413e3/go.mod h1:Yl+fi1br7+Rr3LqpNJf1/uxUdtRUV+Tnj0o93V2B9MU= +dmitri.shuralyov.com/html/belt v0.0.0-20180602232347-f7d459c86be0/go.mod h1:JLBrvjyP0v+ecvNYvCpyZgu5/xkfAUhi6wJj28eUfSU= +dmitri.shuralyov.com/service/change v0.0.0-20181023043359-a85b471d5412/go.mod h1:a1inKt/atXimZ4Mv927x+r7UpyzRUf4emIoiiSC2TN4= +dmitri.shuralyov.com/state v0.0.0-20180228185332-28bcc343414c/go.mod h1:0PRwlb0D6DFvNNtx+9ybjezNCa8XF0xaYcETyp6rHWU= git.apache.org/thrift.git v0.0.0-20180902110319-2566ecd5d999/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= git.apache.org/thrift.git v0.12.0/go.mod h1:fPE2ZNJGynbRyZ4dJvy6G277gSllfV2HJqblrnkyeyg= github.com/Azure/azure-sdk-for-go v26.5.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-autorest v11.5.2+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/DataDog/datadog-go v2.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/Masterminds/glide v0.13.2/go.mod h1:STyF5vcenH/rUqTEv+/hBXlSTo7KYwg2oc2f4tzPWic= github.com/Masterminds/goutils v1.1.0 h1:zukEsf/1JZwCMgHiK3GZftabmxiCw4apj3a28RPBiVg= github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver v1.4.2 h1:WBLTQ37jOCzSLtXNdoo8bNM8876KhNqOKvrlGITgsTc= github.com/Masterminds/semver v1.4.2/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -github.com/Masterminds/semver/v3 v3.0.3 h1:znjIyLfpXEDQjOIEWh+ehwpTU14UzUPub3c3sm36u14= -github.com/Masterminds/semver/v3 v3.0.3/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= -github.com/Masterminds/sprig/v3 v3.0.2 h1:wz22D0CiSctrliXiI9ZO3HoNApweeRGftyDN+BQa3B8= -github.com/Masterminds/sprig/v3 v3.0.2/go.mod h1:oesJ8kPONMONaZgtiHNzUShJbksypC5kWczhZAf6+aU= +github.com/Masterminds/semver/v3 v3.1.0 h1:Y2lUDsFKVRSYGojLJ1yLxSXdMmMYTYls0rCvoqmMUQk= +github.com/Masterminds/semver/v3 v3.1.0/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.1.0 h1:j7GpgZ7PdFqNsmncycTHsLmVPf5/3wJtlgW9TNDYD9Y= +github.com/Masterminds/sprig/v3 v3.1.0/go.mod h1:ONGMf7UfYGAbMXCZmQLy8x3lCDIPrEZE/rU8pmrbihA= github.com/Masterminds/squirrel v1.1.0 h1:baP1qLdoQCeTw3ifCdOq2dkYc6vGcmRdaociKLbEJXs= github.com/Masterminds/squirrel v1.1.0/go.mod h1:yaPeOnPG5ZRwL9oKdTsO/prlkPbXWZlRVMQ/gGlzIuA= github.com/Masterminds/vcs v1.13.0/go.mod h1:N09YCmOQr6RLxC6UNHzuVwAdodYbbnycGHSmwVJjcKA= github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= github.com/PaulARoy/azurestoragecache v0.0.0-20170906084534-3c249a3ba788/go.mod h1:lY1dZd8HBzJ10eqKERHn3CU59tfhzcAVb2c0ZhIWSOk= +github.com/RackSec/srslog v0.0.0-20180709174129-a4725f04ec91/go.mod h1:cDLGBht23g0XQdLjzn6xOGXDkLK182YfINAaZEQLCHQ= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6 h1:bZ28Hqta7TFAK3Q08CMvv8y3/8ATaEqv2nGoc6yff6c= +github.com/andybalholm/brotli v0.0.0-20190621154722-5f990b63d2d6/go.mod h1:+lx6/Aqd1kLJ1GQfkvOnaZ1WGmLpMpbprPuIOOZX30U= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQhVx52RsWOnlkpikZr01T/yAVN2gn0861vByNg= +github.com/armon/go-metrics v0.3.0/go.mod h1:zXjbSimjXTd7vOpY8B0/2LpvNvDoXBuplAD+gJD3GYs= github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/avct/uasurfer v0.0.0-20190821150637-906cc7dc6197/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8= +github.com/avct/uasurfer v0.0.0-20191028135549-26b5daa857f1/go.mod h1:noBAuukeYOXa0aXGqxr24tADqkwDO2KRD15FsuaZ5a8= github.com/aws/aws-sdk-go v1.19.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/beevik/etree v1.1.0/go.mod h1:r8Aw8JqVegEf0w2fDnATrX9VpkMcyFeM0FhwO62wh+A= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= @@ -46,10 +55,14 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= +github.com/buger/jsonparser v0.0.0-20181115193947-bf1c66bbce23/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= github.com/census-instrumentation/opencensus-proto v0.2.0/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkbQ3slBdOA= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= @@ -64,23 +77,33 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/dgoogauth v0.0.0-20190221195224-5a805980a5f3/go.mod h1:hEfFauPHz7+NnjR/yHJGhrKo1Za+zStgwUETx3yzqgY= github.com/die-net/lrucache v0.0.0-20181227122439-19a39ef22a11/go.mod h1:ew0MSjCVDdtGMjF3kzLK9hwdgF5mOE8SbYVF3Rc7mkU= github.com/disintegration/imaging v1.6.0/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= -github.com/disintegration/imaging v1.6.1/go.mod h1:xuIt+sRxDFrHS0drzXUlCJthkJ8k7lkkUojDSR247MQ= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0 h1:8xPHl4/q1VyqGIPif1F+1V3Y3lSmrq01EabUW3CoW5s= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= -github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/francoispqt/gojay v1.2.13/go.mod h1:ehT5mTG4ua4581f1++1WLG0vPdaA9HaiDsoyrBGkyDY= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.1.1/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3 h1:QW2p25fGTu/S0MvEftCo3wV7aEFHBt2m1DTg1HUwh+o= +github.com/go-asn1-ber/asn1-ber v1.3.2-0.20191121212151-29be175fc3a3/go.mod h1:hEBeB/ic+5LoWskz+yKT7vGhhPYkProFKoKdwZRWMe0= +github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-gorp/gorp v2.0.0+incompatible h1:dIQPsBtl6/H1MjVseWuWPXa7ET4p6Dve4j3Hg+UjqYw= github.com/go-gorp/gorp v2.0.0+incompatible/go.mod h1:7IfkAQnO7jfT/9IQ3R9wL1dFhukN6aQxzKTHnkxzA/E= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= @@ -88,12 +111,13 @@ github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA= -github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721 h1:KRMr9A3qfbVM7iV/WcLY/rL5LICqwMHLhwRXKu99fXw= +github.com/golang/gddo v0.0.0-20190419222130-af0f2af80721/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= @@ -106,11 +130,17 @@ github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/gomodule/redigo v2.0.0+incompatible/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-github v17.0.0+incompatible h1:N0LgJ1j65A7kfXrZnUDaYCs/Sf4rEjNlfyDHW9dolSY= github.com/google/go-github v17.0.0+incompatible/go.mod h1:zLgOLi98H3fifZn+44m+umXrS52loVEgC2AApnigrVQ= github.com/google/go-github/v31 v31.0.0 h1:JJUxlP9lFK+ziXKimTCprajMApV1ecWD4NB6CCb0plo= @@ -120,11 +150,13 @@ github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY= +github.com/googleapis/gax-go/v2 v2.0.3/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= @@ -144,12 +176,13 @@ github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:Fecb github.com/grpc-ecosystem/grpc-gateway v1.5.0/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.6.2/go.mod h1:RSKVYQBd5MCa4OVpNdGskqpgL2+G+NZTnrVHpWWfpdw= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= +github.com/h2non/go-is-svg v0.0.0-20160927212452-35e8c4b0612c/go.mod h1:ObS/W+h8RYb1Y7fYivughjxojTmIu5iAIjSrSLCLeqE= +github.com/hako/durafmt v0.0.0-20191009132224-3f39dc1ed9f4/go.mod h1:5Scbynm8dF1XAPwIwkGPqzkM/shndPm79Jd1003hTjE= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= -github.com/hashicorp/go-hclog v0.9.2 h1:CG6TE5H9/JXsFWJCfoIVpKFIkFe6ysEuHirp4DxCsHI= -github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-hclog v0.12.0 h1:d4QkX8FRTYaKaCZBoXYY8zJX2BXjWxurN/GA2tkrmZM= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.1.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= @@ -164,7 +197,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hashicorp/memberlist v0.1.5/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= @@ -173,23 +206,30 @@ github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d h1:W+SIwDdl3+jXWei github.com/hashicorp/yamux v0.0.0-20190923154419-df201c70410d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/huandu/xstrings v1.2.0 h1:yPeWdRnmynF7p+lLYz0H2tthW9lqhMJrQV/U7yy4wX0= -github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/icrowley/fake v0.0.0-20180203215853-4178557ae428/go.mod h1:uhpZMVGznybq1itEKXj6RYw9I71qK4kH+OGMjRC4KEo= -github.com/imdario/mergo v0.3.7 h1:Y+UAYTZ7gDEuOfhxKWy+dvb5dRQ6rJjFSdX2HZY1/gI= -github.com/imdario/mergo v0.3.7/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= +github.com/imdario/mergo v0.3.8 h1:CGgOkSJeqMRmt0D9XLWExdT4m4F1vd3FV3VPt+0VxkQ= +github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/jamiealquiza/envy v1.1.0/go.mod h1:MP36BriGCLwEHhi1OU8E9569JNZrjWfCvzG7RsPnHus= github.com/jaytaylor/html2text v0.0.0-20190408195923-01ec452cbe43/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.9.2 h1:LfVyl+ZlLlLDeQ/d2AqfGIIH4qEDu0Ed2S5GyhCWIWY= +github.com/klauspost/compress v1.9.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/pgzip v1.2.1 h1:oIPZROsWuPHpOdMVWLuJZXwgjhrW8r1yEX8UqMyeNHM= +github.com/klauspost/pgzip v1.2.1/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -204,33 +244,45 @@ github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtB github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk= github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lib/pq v1.2.0 h1:LXpIM/LZ5xGFhOpXAQUIMM1HdyqzVYM13zNdjCEEcA0= -github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lunixbochs/vtclean v1.0.0/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20180730094502-03f2033d19d5/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190312143242-1de009706dbe/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/marstr/guid v0.0.0-20170427235115-8bdf7d1a087c/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= github.com/mattermost/go-i18n v1.11.0 h1:1hLKqn/ZvhZ80OekjVPGYcCrBfMz+YxNNgqS+beL7zE= github.com/mattermost/go-i18n v1.11.0/go.mod h1:RyS7FDNQlzF1PsjbJWHRI35exqaKGSO9qD4iv8QjE34= github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible h1:FN4zK2wNig7MVVsOsGEZ+LeIq0gUcudn3LEGgbodMq8= github.com/mattermost/gorp v2.0.1-0.20190301154413-3b31e9a39d05+incompatible/go.mod h1:0kX1qa3DOpaPJyOdMLeo7TcBN0QmUszj9a/VygOhDe0= -github.com/mattermost/ldap v3.0.4+incompatible h1:SOeNnz+JNR+foQ3yHkYqijb9MLPhXN2BZP/PdX23VDU= -github.com/mattermost/ldap v3.0.4+incompatible/go.mod h1:b4reDCcGpBxJ4WX0f224KFY+OR0npin7or7EFpeIko4= -github.com/mattermost/mattermost-server/v5 v5.18.0 h1:In1v/1vrYMEDRHiXe6sA50KJ5WGkE2mLtsIz7YOGsN8= -github.com/mattermost/mattermost-server/v5 v5.18.0/go.mod h1:10vIYDohcPNDMDk3XlRUjNO9nGk9fnFHEFeSIUYR82k= +github.com/mattermost/gosaml2 v0.3.2/go.mod h1:Z429EIOiEi9kbq6yHoApfzlcXpa6dzRDc6pO+Vy2Ksk= +github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d h1:2DV7VIlEv6J5R5o6tUcb3ZMKJYeeZuWZL7Rv1m23TgQ= +github.com/mattermost/ldap v0.0.0-20191128190019-9f62ba4b8d4d/go.mod h1:HLbgMEI5K131jpxGazJ97AxfPDt31osq36YS1oxFQPQ= +github.com/mattermost/mattermost-server/v5 v5.23.0 h1:iXa6+ht9GcmMR1EwhJAb30sXOJN3pYg1DuqUsiv7JuI= +github.com/mattermost/mattermost-server/v5 v5.23.0/go.mod h1:nMrt08IvThjybZpXPe/nqe/oJuvJxhqKkGI+m7M0R00= github.com/mattermost/rsc v0.0.0-20160330161541-bbaefb05eaa0/go.mod h1:nV5bfVpT//+B1RPD2JvRnxbkLmJEYXmRaaVl15fsXjs= github.com/mattermost/viper v1.0.4/go.mod h1:uc5hKG9lv4/KRwPOt2c1omOyirS/UnuA2TytiZQSFHM= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/mattn/go-sqlite3 v1.11.0 h1:LDdKkqtYlom37fkvqs8rMPFKAMe8+SgjbwZ6ex1/A/Q= github.com/mattn/go-sqlite3 v1.11.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/mholt/archiver/v3 v3.3.0 h1:vWjhY8SQp5yzM9P6OJ/eZEkmi3UAbRrxCq48MxjAzig= +github.com/mholt/archiver/v3 v3.3.0/go.mod h1:YnQtqsp+94Rwd0D/rk5cnLrxusUBUXg+08Ebtr1Mqao= +github.com/microcosm-cc/bluemonday v1.0.1/go.mod h1:hsXNsILzKxV+sX77C5b8FSuKF00vh2OMYv+xgHpAMF4= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/miekg/dns v1.1.19/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= -github.com/minio/minio-go/v6 v6.0.40/go.mod h1:qD0lajrGW49lKZLtXKtCB4X/qkMf0a5tBvN2PaZg7Gg= +github.com/miekg/dns v1.1.27/go.mod h1:KNUDUusw/aVsxyTYZM1oqvCicbwhgbNgztCETuNZ7xM= +github.com/minio/minio-go/v6 v6.0.53/go.mod h1:DIvC/IApeHX8q1BAMVCXSXwpmrmM+I+iBvhvztQorfI= github.com/minio/sha256-simd v0.1.1/go.mod h1:B5e1o+1/KgNmWrSQK08Y6Z1Vb5pwIktudl0J58iy0KM= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= @@ -251,18 +303,22 @@ github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3Rllmb github.com/muesli/smartcrop v0.2.1-0.20181030220600-548bbf0c0965/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= github.com/muesli/smartcrop v0.3.0/go.mod h1:i2fCI/UorTfgEpPPLWiFBv4pye+YAG78RwcQLUkocpI= github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/neelance/astrewrite v0.0.0-20160511093645-99348263ae86/go.mod h1:kHJEU3ofeGjhHklVoIGuVj85JJwZ6kWPaJwCIxgnFmo= +github.com/neelance/sourcemap v0.0.0-20151028013722-8c68805598ab/go.mod h1:Qr6/a/Q4r9LP1IltGz7tA7iOK1WonHEYhu1HRBA7ZiM= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= +github.com/nwaples/rardecode v1.0.0 h1:r7vGuS5akxOnR4JQSkko62RJ1ReCMXxQRPtxsiFMBOs= +github.com/nwaples/rardecode v1.0.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA= +github.com/olivere/elastic v6.2.27+incompatible/go.mod h1:J+q1zQJTgAz9woqsbVRqGeB5G1iqDKVBWLNSYW8yfJ8= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= -github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= -github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.3/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8= github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= @@ -271,13 +327,16 @@ github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144T github.com/pborman/uuid v1.2.0 h1:J7Q5mO4ysT1dv8hyrUGHb9+ooztCXu1D8MY8DZYsu3g= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg= -github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/pelletier/go-toml v1.6.0 h1:aetoXYr0Tv7xRU/V4B4IZJ2QcbtMUFoNb3ORp7TzIK4= +github.com/pelletier/go-toml v1.6.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys= github.com/peterbourgon/diskv v0.0.0-20171120014656-2973218375c3/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM8aXeqhl0I= github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= @@ -286,27 +345,29 @@ github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXP github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/prometheus/procfs v0.0.0-20180725123919-05ee40e3a273/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU= +github.com/rudderlabs/analytics-go v3.1.0+incompatible/go.mod h1:LF8/ty9kUX4PTY3l5c97K3nZZaX5Hwsvt+NBaRL/f30= +github.com/russellhaering/goxmldsig v0.0.0-20180430223755-7acd5e4a6ef7/go.mod h1:Oz4y6ImuOQZxynhbSXk7btjEfNBtGlj2dcaOvXl2FSM= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/rwcarlsen/goexif v0.0.0-20190318171057-76e3344f7516/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= @@ -315,17 +376,44 @@ github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0 github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/segmentio/analytics-go v3.1.0+incompatible/go.mod h1:C7CYBtQWk4vRk2RyLu0qOcbHJ18E3F1HV2C/8JvKN48= github.com/segmentio/backo-go v0.0.0-20160424052352-204274ad699c/go.mod h1:kJ9mm9YmoWSkk+oQ+5Cj8DEoRCX2JT6As4kEtIIOp1M= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shurcooL/component v0.0.0-20170202220835-f88ec8f54cc4/go.mod h1:XhFIlyj5a1fBNx5aJTbKoIq0mNaPvOagO+HjB3EtxrY= +github.com/shurcooL/events v0.0.0-20181021180414-410e4ca65f48/go.mod h1:5u70Mqkb5O5cxEA8nxTsgrgLehJeAw6Oc4Ab1c/P1HM= +github.com/shurcooL/github_flavored_markdown v0.0.0-20181002035957-2122de532470/go.mod h1:2dOwnU2uBioM+SGy2aZoq1f/Sd1l9OkAeAUvjSyvgU0= +github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= +github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= +github.com/shurcooL/gofontwoff v0.0.0-20180329035133-29b52fc0a18d/go.mod h1:05UtEgK5zq39gLST6uB0cf3NEHjETfB4Fgr3Gx5R9Vw= +github.com/shurcooL/gopherjslib v0.0.0-20160914041154-feb6d3990c2c/go.mod h1:8d3azKNyqcHP1GaQE/c6dDgjkgSx2BZ4IoEi4F1reUI= +github.com/shurcooL/highlight_diff v0.0.0-20170515013008-09bb4053de1b/go.mod h1:ZpfEhSmds4ytuByIcDnOLkTHGUI6KNqRNPDLHDk+mUU= +github.com/shurcooL/highlight_go v0.0.0-20181028180052-98c3abbbae20/go.mod h1:UDKB5a1T23gOMUJrI+uSuH0VRDStOiUVSjBTRDVBVag= +github.com/shurcooL/home v0.0.0-20181020052607-80b7ffcb30f9/go.mod h1:+rgNQw2P9ARFAs37qieuu7ohDNQ3gds9msbT2yn85sg= +github.com/shurcooL/htmlg v0.0.0-20170918183704-d01228ac9e50/go.mod h1:zPn1wHpTIePGnXSHpsVPWEktKXHr6+SS6x/IKRb7cpw= +github.com/shurcooL/httperror v0.0.0-20170206035902-86b7830d14cc/go.mod h1:aYMfkZ6DWSJPJ6c4Wwz3QtW22G7mf/PEgaB9k/ik5+Y= +github.com/shurcooL/httpfs v0.0.0-20171119174359-809beceb2371/go.mod h1:ZY1cvUeJuFPAdZ/B6v7RHavJWZn2YPVFQ1OSXhCGOkg= +github.com/shurcooL/httpgzip v0.0.0-20180522190206-b1c53ac65af9/go.mod h1:919LwcH0M7/W4fcZ0/jy0qGght1GIhqyS/EgWGH2j5Q= +github.com/shurcooL/issues v0.0.0-20181008053335-6292fdc1e191/go.mod h1:e2qWDig5bLteJ4fwvDAc2NHzqFEthkqn7aOZAOpj+PQ= +github.com/shurcooL/issuesapp v0.0.0-20180602232740-048589ce2241/go.mod h1:NPpHK2TI7iSaM0buivtFUc9offApnI0Alt/K8hcHy0I= +github.com/shurcooL/notifications v0.0.0-20181007000457-627ab5aea122/go.mod h1:b5uSkrEVM1jQUspwbixRBhaIjIzL2xazXp6kntxYle0= +github.com/shurcooL/octicon v0.0.0-20181028054416-fa4f57f9efb2/go.mod h1:eWdoE5JD4R5UVWDucdOPg1g2fqQRq78IQa9zlOV1vpQ= +github.com/shurcooL/reactions v0.0.0-20181006231557-f2e0b4ca5b82/go.mod h1:TCR1lToEk4d2s07G3XGfz2QrgHXg4RJBvjrOozvoWfk= +github.com/shurcooL/sanitized_anchor_name v0.0.0-20170918181015-86672fcb3f95/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/shurcooL/users v0.0.0-20180125191416-49c67e49c537/go.mod h1:QJTqeLYEDaXHZDBsXlPCDqdhQuJkuw4NOtaxYe3xii4= +github.com/shurcooL/webdavfs v0.0.0-20170829043945-18c3829fa133/go.mod h1:hKmq5kWdCj2z2KEozexVbfEZIWiTjhE0+UjmZgPqehw= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v1.0.0/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM= -github.com/smartystreets/go-aws-auth v0.0.0-20180515143844-0c1422d1fdb9/go.mod h1:SnhjPscd9TpLiy1LpzGSKh3bXCfxxXuqd9xmQJy3slM= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/smartystreets/goconvey v0.0.0-20190710185942-9d28bd7c0945/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sourcegraph/annotate v0.0.0-20160123013949-f4cad6c6324d/go.mod h1:UdhH50NIW0fCiwBSr0co2m7BnFLdv4fQTgdqdJTHFeE= +github.com/sourcegraph/syntaxhighlight v0.0.0-20170531221838-bd320f5d308e/go.mod h1:HuIsMU8RRBOtsCgI77wP899iHVBQpCmg4ErYMZB+2IA= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= @@ -342,11 +430,24 @@ github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/tarm/serial v0.0.0-20180830185346-98f6abe2eb07/go.mod h1:kDXzergiv9cbyO7IOYJZWg1U88JhDg3PB6klq9Hg2pA= github.com/throttled/throttled v2.2.4+incompatible/go.mod h1:0BjlrEGQmvxps+HuXLsyRdqpSRvJpq0PNIsOtqP9Nos= github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/tylerb/graceful v1.2.15/go.mod h1:LPYTbOYmUTdabwRt0TGhLllQ0MUNbs0Y5q1WXJOI9II= +github.com/uber/jaeger-client-go v2.22.1+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk= +github.com/uber/jaeger-lib v2.2.0+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/viant/assertly v0.4.8/go.mod h1:aGifi++jvCrUaklKEKT0BU95igDNaqkvz+49uaYMPRU= +github.com/viant/toolbox v0.24.0/go.mod h1:OxMCG57V0PXuIP2HNQrtJf2CjqdmbrOx5EkMILuUhzM= +github.com/wiggin77/cfg v1.0.2/go.mod h1:b3gotba2e5bXTqTW48DwIFoLc+4lWKP7WPi/CdvZ4aE= +github.com/wiggin77/logr v1.0.4/go.mod h1:h98FF6GPfThhDrHCg063hZA1sIyOEzQ/P85wgqI0IqE= +github.com/wiggin77/merror v1.0.2/go.mod h1:uQTcIU0Z6jRK4OwqganPYerzQxSFJ4GSHM3aurxxQpg= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= +github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c/go.mod h1:UrdRz5enIKZ63MEE3IF9l2/ebyx59GyGgPi+tICQdmM= github.com/ziutek/mymysql v1.5.4 h1:GB0qdRGsTwQSBVYuVShFBKaXSnSnYYC2d9knnE1LHFs= @@ -354,36 +455,49 @@ github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wK go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA= go.opencensus.io v0.19.1/go.mod h1:gug0GbSHa8Pafr0d2urOSgoXHZ6x/RUlaiT0d9pqb4A= go.opencensus.io v0.19.2/go.mod h1:NO/8qkisMZLZ1FCsKNqtJPwc8/TaclWyY0B6wcYNg9M= -go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.2.0 h1:6I+W7f5VwC5SV9dNrZ3qXrDB9mD0dyGOi/ZJmYw03T4= -go.uber.org/multierr v1.2.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.5.1 h1:rsqfU5vBkVknbhUGbAUwQKR2H4ItV8tjJ+6kJX4cxHM= +go.uber.org/atomic v1.5.1/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.4.0 h1:f3WCSC2KzAcBXGATIxAB1E2XuCpNU255wNKZ505qi3E= +go.uber.org/multierr v1.4.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1yOyC1qaOBpL57BhE= +golang.org/x/build v0.0.0-20190111050920-041ab4dc3f9d/go.mod h1:OWs+y06UdEOHN4y+MfF/py+xQ/tYqIWW03b70/CG9Rw= golang.org/x/build v0.0.0-20190314133821-5284462c4bec/go.mod h1:atTaCNAy0f16Ah5aV1gMSwgiKVHwu/JncqDpuRr7lS4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181030102418-4d3f4d9ffa16/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190513172903-22d7a77e9e5f/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190911031432-227b76d455e7/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= -golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba h1:9bFeDpN3gTqNanMVqNcoR/pJQuP5uroC3t1D7eXozTE= -golang.org/x/crypto v0.0.0-20191119213627-4f8c1d86b1ba/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200220183623-bac4c82f6975/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904 h1:bXoxMPcSLOq08zI3/c5dEBT6lE4eh+jOh886GHrn6V8= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190910094157-69e4b8554b2a/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20200119044424-58c23975cae1/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20181217174547-8f45f776aaf1/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f h1:J5lckAjkw6qYlOZNj90mLYNTEKDvWeuc1yieZ8qUzUE= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -398,29 +512,30 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190313220215-9f648a60d977/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190322120337-addf6b3196f6/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 h1:MlY3mEfbnWGmUi4rtHOtNnnnN4UJRGSyLPx+DXA5Sq4= -golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181017192945-9dcd33a902f4/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181203162652-d668ce993890/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190319182350-c85d3e98c914/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -434,16 +549,19 @@ golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/sys v0.0.0-20181218192612-074acd46bca6/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190316082340-a2f829d7f35f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190322080309-f49334f85ddc/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d h1:+R4KGOnez64A81RvjARKc4UT5/tI9ujCIVX+P5KiHuI= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed h1:5TJcLJn2a55mJjzYk0yOoqN8X1OdvBDUnaZaKKyQtkY= -golang.org/x/sys v0.0.0-20191002091554-b397fe3ad8ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -453,6 +571,7 @@ golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxb golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181030000716-a0a13e073c7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20181219222714-6e267b5cc78e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -460,54 +579,67 @@ golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191002161851-3769738f410b/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216052735-49a3e744a425/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200213050514-49b8ac185c84 h1:0QCtZnPx0LFDcPMUX7Qg328Twbm3c/Jx1d0XT/x9jcg= +golang.org/x/tools v0.0.0-20200213050514-49b8ac185c84/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/api v0.0.0-20180910000450-7ca32eb868bf/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181030000543-1d582fd0359e/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= google.golang.org/api v0.0.0-20181220000619-583d854617af/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= +google.golang.org/api v0.1.0/go.mod h1:UGEZY7KEX120AnNLIHFMKIo4obdJhkp2tPbaPlQx13Y= google.golang.org/api v0.2.0/go.mod h1:IfRCZScioGtypHNTlz3gFk67J8uePVW7uDTBzXuIkhU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.3.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.4 h1:WiKh4+/eMB2HaY7QhCfW/R7MuRAoA8QMCSJA6jP5/fo= -google.golang.org/appengine v1.6.4/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20181029155118-b69ba1387ce2/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20181202183823-bd91e49a0898/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= google.golang.org/genproto v0.0.0-20181219182458-5a97ab628bfb/go.mod h1:7Ep/1NZk928CDR8SjdVbjWNpdIf6nzjE3BTgJDr2Atg= +google.golang.org/genproto v0.0.0-20190306203927-b5d61aea6440/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190321212433-e79c0c59cdb5/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= -google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b h1:c8OBoXP3kTbDWWB/oVE3FkR851p4iZ3MPadz7zXEIPU= +google.golang.org/genproto v0.0.0-20200128133413-58ce757ed39b/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.14.0 h1:ArxJuB1NWfPY6r9Gp9gqwplT0Ge7nqv9msgu03lHLmo= google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio= google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.19.1/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.24.0 h1:vb/1TCsVn3DcJlQ0Gs1yB1pKI6Do2/QNwxdKqmc/b0s= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc/go.mod h1:m7x9LTH6d71AHyAX77c9yqWCCa3UKHcVEj9y7hAtKDk= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= -gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/ini.v1 v1.48.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.51.1/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mail.v2 v2.3.1/go.mod h1:htwXN1Qh09vZJ1NVKxQqHPBaCBbzKhp5GzuJEA4VJWw= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/olivere/elastic.v5 v5.0.82/go.mod h1:uhHoB4o3bvX5sorxBU29rPcmBQdV2Qfg0FBrx5D6pV0= +gopkg.in/olivere/elastic.v6 v6.2.27/go.mod h1:2cTT8Z+/LcArSWpCgvZqBgt3VOqXiy7v00w12Lz8bd4= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= @@ -515,13 +647,19 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= grpc.go4.org v0.0.0-20170609214715-11d0a25b4919/go.mod h1:77eQGdRu53HpSqPFJFmuJdjuHRquDANNeA4x7B8WQ9o= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20180920025451-e3ad64cb4ed3/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +sourcegraph.com/sourcegraph/go-diff v0.5.0/go.mod h1:kuch7UrkMzY0X+p9CRK03kfuPQ2zzQcaEFbx8wA8rck= +sourcegraph.com/sqs/pbtypes v0.0.0-20180604144634-d3ebe8f20ae4/go.mod h1:ketZ/q3QxT9HOBeFhu6RdvsftgpsbFHBF5Cas6cDKZ0= willnorris.com/go/gifresize v1.0.0/go.mod h1:eBM8gogBGCcaH603vxSpnfjwXIpq6nmnj/jauBDKtAk= willnorris.com/go/imageproxy v0.9.0/go.mod h1:SVC/wfHtCS4kjk3llMeuV4KlTN3a8XTgFWI8o7i3Avg= diff --git a/server/api.go b/server/api.go index 83183bec8..7f83fb40f 100644 --- a/server/api.go +++ b/server/api.go @@ -10,20 +10,17 @@ import ( "sync" "time" - "github.com/pkg/errors" - + "github.com/google/go-github/v31/github" "github.com/gorilla/mux" - "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" "github.com/mattermost/mattermost-server/v5/plugin" - - "github.com/google/go-github/v31/github" + "github.com/pkg/errors" "golang.org/x/oauth2" ) const ( - API_ERROR_ID_NOT_CONNECTED = "not_connected" + apiErrorIDNotConnected = "not_connected" // the OAuth token expiry in seconds TokenTTL = 10 * 60 ) @@ -54,39 +51,66 @@ type PRDetails struct { type HTTPHandlerFuncWithUser func(w http.ResponseWriter, r *http.Request, userID string) -func writeAPIError(w http.ResponseWriter, err *APIErrorResponse) { - b, _ := json.Marshal(err) - w.WriteHeader(err.StatusCode) - w.Write(b) +func (p *Plugin) writeJSON(w http.ResponseWriter, v interface{}) { + b, err := json.Marshal(v) + if err != nil { + p.API.LogWarn("Failed to marshal JSON response", "error", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + _, err = w.Write(b) + if err != nil { + p.API.LogWarn("Failed to write JSON response", "error", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } +} + +func (p *Plugin) writeAPIError(w http.ResponseWriter, apiErr *APIErrorResponse) { + b, err := json.Marshal(apiErr) + if err != nil { + p.API.LogWarn("Failed to marshal API error", "error", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } + + w.WriteHeader(apiErr.StatusCode) + + _, err = w.Write(b) + if err != nil { + p.API.LogWarn("Failed to write JSON response", "error", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } } -func (p *Plugin) initialiseAPI() { +func (p *Plugin) initializeAPI() { p.router = mux.NewRouter() oauthRouter := p.router.PathPrefix("/oauth").Subrouter() apiRouter := p.router.PathPrefix("/api/v1").Subrouter() - p.router.HandleFunc("/webhook", p.handleWebhook).Methods("POST") - - oauthRouter.HandleFunc("/connect", p.extractUserMiddleWare(p.connectUserToGitHub, false)).Methods("GET") - oauthRouter.HandleFunc("/complete", p.extractUserMiddleWare(p.completeConnectUserToGitHub, false)).Methods("GET") - - apiRouter.HandleFunc("/connected", p.extractUserMiddleWare(p.getConnected, true)).Methods("GET") - apiRouter.HandleFunc("/todo", p.extractUserMiddleWare(p.postToDo, true)).Methods("POST") - apiRouter.HandleFunc("/reviews", p.extractUserMiddleWare(p.getReviews, false)).Methods("GET") - apiRouter.HandleFunc("/yourprs", p.extractUserMiddleWare(p.getYourPrs, false)).Methods("GET") - apiRouter.HandleFunc("/prsdetails", p.extractUserMiddleWare(p.getPrsDetails, false)).Methods("POST") - apiRouter.HandleFunc("/searchissues", p.extractUserMiddleWare(p.searchIssues, false)).Methods("GET") - apiRouter.HandleFunc("/yourassignments", p.extractUserMiddleWare(p.getYourAssignments, false)).Methods("GET") - apiRouter.HandleFunc("/createissue", p.extractUserMiddleWare(p.createIssue, false)).Methods("POST") - apiRouter.HandleFunc("/createissuecomment", p.extractUserMiddleWare(p.createIssueComment, false)).Methods("POST") - apiRouter.HandleFunc("/mentions", p.extractUserMiddleWare(p.getMentions, false)).Methods("GET") - apiRouter.HandleFunc("/unreads", p.extractUserMiddleWare(p.getUnreads, false)).Methods("GET") - apiRouter.HandleFunc("/repositories", p.extractUserMiddleWare(p.getRepositories, false)).Methods("GET") - apiRouter.HandleFunc("/settings", p.extractUserMiddleWare(p.updateSettings, false)).Methods("POST") - apiRouter.HandleFunc("/user", p.extractUserMiddleWare(p.getGitHubUser, true)).Methods("POST") - apiRouter.HandleFunc("/issue", p.extractUserMiddleWare(p.getIssueByNumber, false)).Methods("GET") - apiRouter.HandleFunc("/pr", p.extractUserMiddleWare(p.getPrByNumber, false)).Methods("GET") + p.router.HandleFunc("/webhook", p.handleWebhook).Methods(http.MethodPost) + + oauthRouter.HandleFunc("/connect", p.extractUserMiddleWare(p.connectUserToGitHub, false)).Methods(http.MethodGet) + oauthRouter.HandleFunc("/complete", p.extractUserMiddleWare(p.completeConnectUserToGitHub, false)).Methods(http.MethodGet) + + apiRouter.HandleFunc("/connected", p.extractUserMiddleWare(p.getConnected, true)).Methods(http.MethodGet) + apiRouter.HandleFunc("/todo", p.extractUserMiddleWare(p.postToDo, true)).Methods(http.MethodPost) + apiRouter.HandleFunc("/reviews", p.extractUserMiddleWare(p.getReviews, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/yourprs", p.extractUserMiddleWare(p.getYourPrs, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/prsdetails", p.extractUserMiddleWare(p.getPrsDetails, false)).Methods(http.MethodPost) + apiRouter.HandleFunc("/searchissues", p.extractUserMiddleWare(p.searchIssues, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/yourassignments", p.extractUserMiddleWare(p.getYourAssignments, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/createissue", p.extractUserMiddleWare(p.createIssue, false)).Methods(http.MethodPost) + apiRouter.HandleFunc("/createissuecomment", p.extractUserMiddleWare(p.createIssueComment, false)).Methods(http.MethodPost) + apiRouter.HandleFunc("/mentions", p.extractUserMiddleWare(p.getMentions, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/unreads", p.extractUserMiddleWare(p.getUnreads, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/repositories", p.extractUserMiddleWare(p.getRepositories, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/settings", p.extractUserMiddleWare(p.updateSettings, false)).Methods(http.MethodPost) + apiRouter.HandleFunc("/user", p.extractUserMiddleWare(p.getGitHubUser, true)).Methods(http.MethodPost) + apiRouter.HandleFunc("/issue", p.extractUserMiddleWare(p.getIssueByNumber, false)).Methods(http.MethodGet) + apiRouter.HandleFunc("/pr", p.extractUserMiddleWare(p.getPrByNumber, false)).Methods(http.MethodGet) } func (p *Plugin) extractUserMiddleWare(handler HTTPHandlerFuncWithUser, jsonResponse bool) http.HandlerFunc { @@ -94,7 +118,7 @@ func (p *Plugin) extractUserMiddleWare(handler HTTPHandlerFuncWithUser, jsonResp userID := r.Header.Get("Mattermost-User-ID") if userID == "" { if jsonResponse { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Not authorized.", StatusCode: http.StatusUnauthorized}) } else { http.Error(w, "Not authorized", http.StatusUnauthorized) } @@ -212,20 +236,20 @@ func (p *Plugin) completeConnectUserToGitHub(w http.ResponseWriter, r *http.Requ GitHubUsername: gitUser.GetLogin(), LastToDoPostAt: model.GetMillis(), Settings: &UserSettings{ - SidebarButtons: SETTING_BUTTONS_TEAM, + SidebarButtons: settingButtonsTeam, DailyReminder: true, Notifications: true, }, AllowedPrivateRepos: state.PrivateAllowed, } - if err := p.storeGitHubUserInfo(userInfo); err != nil { + if err = p.storeGitHubUserInfo(userInfo); err != nil { fmt.Println(err.Error()) http.Error(w, "Unable to connect user to GitHub", http.StatusInternalServerError) return } - if err := p.storeGitHubToUserIDMapping(gitUser.GetLogin(), state.UserID); err != nil { + if err = p.storeGitHubToUserIDMapping(gitUser.GetLogin(), state.UserID); err != nil { fmt.Println(err.Error()) } @@ -247,13 +271,13 @@ func (p *Plugin) completeConnectUserToGitHub(w http.ResponseWriter, r *http.Requ "* The fifth will refresh the numbers.\n\n"+ "Click on them!\n\n"+ "##### Slash Commands\n"+ - strings.Replace(COMMAND_HELP, "|", "`", -1), gitUser.GetLogin(), gitUser.GetHTMLURL()) + strings.Replace(commandHelp, "|", "`", -1), gitUser.GetLogin(), gitUser.GetHTMLURL()) p.CreateBotDMPost(state.UserID, message, "custom_git_welcome") config := p.getConfiguration() p.API.PublishWebSocketEvent( - WS_EVENT_CONNECT, + wsEventConnect, map[string]interface{}{ "connected": true, "github_username": userInfo.GitHubUsername, @@ -279,72 +303,66 @@ func (p *Plugin) completeConnectUserToGitHub(w http.ResponseWriter, r *http.Requ ` w.Header().Set("Content-Type", "text/html") - w.Write([]byte(html)) -} - -type ConnectedResponse struct { - Connected bool `json:"connected"` - GitHubUsername string `json:"github_username"` - GitHubClientID string `json:"github_client_id"` - EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` - Organization string `json:"organization"` - Settings *UserSettings `json:"settings"` -} - -type CreateIssueCommentRequest struct { - PostId string `json:"post_id"` - Owner string `json:"owner"` - Repo string `json:"repo"` - Number int `json:"number"` - Comment string `json:"comment"` -} - -type GitHubUserRequest struct { - UserID string `json:"user_id"` -} - -type GitHubUserResponse struct { - Username string `json:"username"` + _, err = w.Write([]byte(html)) + if err != nil { + p.API.LogWarn("Failed to write HTML response", "error", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + return + } } func (p *Plugin) getGitHubUser(w http.ResponseWriter, r *http.Request, _ string) { + type GitHubUserRequest struct { + UserID string `json:"user_id"` + } + req := &GitHubUserRequest{} - dec := json.NewDecoder(r.Body) - if err := dec.Decode(&req); err != nil || req.UserID == "" { - if err != nil { - mlog.Error("Error decoding JSON body: " + err.Error()) - } - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object with a non-blank user_id field.", StatusCode: http.StatusBadRequest}) + if err := json.NewDecoder(r.Body).Decode(&req); err != nil { + mlog.Error("Error decoding GitHubUserRequest from JSON body", mlog.Err(err)) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + return + } + + if req.UserID == "" { + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object with a non-blank user_id field.", StatusCode: http.StatusBadRequest}) return } userInfo, apiErr := p.getGitHubUserInfo(req.UserID) if apiErr != nil { - if apiErr.ID == API_ERROR_ID_NOT_CONNECTED { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) + if apiErr.ID == apiErrorIDNotConnected { + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) } else { - writeAPIError(w, apiErr) + p.writeAPIError(w, apiErr) } return } if userInfo == nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "User is not connected to a GitHub account.", StatusCode: http.StatusNotFound}) return } - resp := &GitHubUserResponse{Username: userInfo.GitHubUsername} - b, jsonErr := json.Marshal(resp) - if jsonErr != nil { - mlog.Error("Error encoding JSON response: " + jsonErr.Error()) - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Encountered an unexpected error. Please try again.", StatusCode: http.StatusInternalServerError}) + type GitHubUserResponse struct { + Username string `json:"username"` } - w.Write(b) + + resp := &GitHubUserResponse{Username: userInfo.GitHubUsername} + p.writeJSON(w, resp) } func (p *Plugin) getConnected(w http.ResponseWriter, r *http.Request, userID string) { config := p.getConfiguration() + type ConnectedResponse struct { + Connected bool `json:"connected"` + GitHubUsername string `json:"github_username"` + GitHubClientID string `json:"github_client_id"` + EnterpriseBaseURL string `json:"enterprise_base_url,omitempty"` + Organization string `json:"organization"` + Settings *UserSettings `json:"settings"` + } + resp := &ConnectedResponse{ Connected: false, EnterpriseBaseURL: config.EnterpriseBaseURL, @@ -373,73 +391,70 @@ func (p *Plugin) getConnected(w http.ResponseWriter, r *http.Request, userID str if p.HasUnreads(info) { p.PostToDo(info) info.LastToDoPostAt = now - p.storeGitHubUserInfo(info) + if err := p.storeGitHubUserInfo(info); err != nil { + p.API.LogWarn("Failed to store github info for new user", "userID", userID, "error", err.Error()) + } } } } - privateRepoStoreKey := info.UserID + GITHUB_PRIVATE_REPO_KEY + privateRepoStoreKey := info.UserID + githubPrivateRepoKey if config.EnablePrivateRepo && !info.AllowedPrivateRepos { - hasBeenNotified := false - if val, err := p.API.KVGet(privateRepoStoreKey); err == nil { - hasBeenNotified = val != nil - } else { + val, err := p.API.KVGet(privateRepoStoreKey) + if err != nil { mlog.Error("Unable to get private repo key value, err=" + err.Error()) + return } - if !hasBeenNotified { + // Inform the user once that private repositories enabled + if val == nil { p.CreateBotDMPost(info.UserID, "Private repositories have been enabled for this plugin. To be able to use them you must disconnect and reconnect your GitHub account. To reconnect your account, use the following slash commands: `/github disconnect` followed by `/github connect private`.", "") - if err := p.API.KVSet(privateRepoStoreKey, []byte("1")); err != nil { + + err := p.API.KVSet(privateRepoStoreKey, []byte("1")) + if err != nil { mlog.Error("Unable to set private repo key value, err=" + err.Error()) } } } } - b, _ := json.Marshal(resp) - w.Write(b) + p.writeJSON(w, resp) } func (p *Plugin) getMentions(w http.ResponseWriter, r *http.Request, userID string) { config := p.getConfiguration() - ctx := context.Background() - - var githubClient *github.Client - username := "" - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) - username = info.GitHubUsername } - result, _, err := githubClient.Search.Issues(ctx, getMentionSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) + githubClient := p.githubConnect(*info.Token) + username := info.GitHubUsername + + result, _, err := githubClient.Search.Issues(context.Background(), getMentionSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return } - resp, _ := json.Marshal(result.Issues) - w.Write(resp) + p.writeJSON(w, result.Issues) } func (p *Plugin) getUnreads(w http.ResponseWriter, r *http.Request, userID string) { - ctx := context.Background() - - var githubClient *github.Client - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) } - notifications, _, err := githubClient.Activity.ListNotifications(ctx, &github.NotificationListOptions{}) + githubClient := p.githubConnect(*info.Token) + + notifications, _, err := githubClient.Activity.ListNotifications(context.Background(), &github.NotificationListOptions{}) if err != nil { mlog.Error(err.Error()) + return } type filteredNotification struct { @@ -450,7 +465,7 @@ func (p *Plugin) getUnreads(w http.ResponseWriter, r *http.Request, userID strin filteredNotifications := []*filteredNotification{} for _, n := range notifications { - if n.GetReason() == "subscribed" { + if n.GetReason() == notificationReasonSubscribed { continue } @@ -464,80 +479,70 @@ func (p *Plugin) getUnreads(w http.ResponseWriter, r *http.Request, userID strin }) } - resp, _ := json.Marshal(filteredNotifications) - w.Write(resp) + p.writeJSON(w, filteredNotifications) } func (p *Plugin) getReviews(w http.ResponseWriter, r *http.Request, userID string) { config := p.getConfiguration() - ctx := context.Background() - - var githubClient *github.Client - username := "" - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) - username = info.GitHubUsername } - result, _, err := githubClient.Search.Issues(ctx, getReviewSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) + githubClient := p.githubConnect(*info.Token) + username := info.GitHubUsername + + result, _, err := githubClient.Search.Issues(context.Background(), getReviewSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return } - resp, _ := json.Marshal(result.Issues) - w.Write(resp) + p.writeJSON(w, result.Issues) } func (p *Plugin) getYourPrs(w http.ResponseWriter, r *http.Request, userID string) { config := p.getConfiguration() - ctx := context.Background() - - var githubClient *github.Client - username := "" - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) - username = info.GitHubUsername } - result, _, err := githubClient.Search.Issues(ctx, getYourPrsSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) + githubClient := p.githubConnect(*info.Token) + username := info.GitHubUsername + + result, _, err := githubClient.Search.Issues(context.Background(), getYourPrsSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return } - resp, _ := json.Marshal(result.Issues) - w.Write(resp) + p.writeJSON(w, result.Issues) } func (p *Plugin) getPrsDetails(w http.ResponseWriter, r *http.Request, userID string) { - ctx := context.Background() - - var githubClient *github.Client - info, err := p.getGitHubUserInfo(userID) - if err != nil { - writeAPIError(w, err) + p.writeAPIError(w, err) return } - githubClient = p.githubConnect(*info.Token) + githubClient := p.githubConnect(*info.Token) var prList []*PRDetails - json.NewDecoder(r.Body).Decode(&prList) + if err := json.NewDecoder(r.Body).Decode(&prList); err != nil { + mlog.Error("Error decoding PRDetails JSON body", mlog.Err(err)) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + return + } prDetails := make([]*PRDetails, len(prList)) + ctx := context.Background() var wg sync.WaitGroup - for i, pr := range prList { i := i pr := pr @@ -551,8 +556,7 @@ func (p *Plugin) getPrsDetails(w http.ResponseWriter, r *http.Request, userID st wg.Wait() - resp, _ := json.Marshal(prDetails) - w.Write(resp) + p.writeJSON(w, prDetails) } func fetchPRDetails(ctx context.Context, client *github.Client, prURL string, prNumber int) *PRDetails { @@ -625,37 +629,29 @@ func getRepoOwnerAndNameFromURL(url string) (string, string) { func (p *Plugin) searchIssues(w http.ResponseWriter, r *http.Request, userID string) { config := p.getConfiguration() - if r.Method != http.MethodGet { - http.Error(w, fmt.Sprintf("Request: %s is not allowed, must be GET", r.Method), http.StatusMethodNotAllowed) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return } - ctx := context.Background() - - var githubClient *github.Client + githubClient := p.githubConnect(*info.Token) searchTerm := r.FormValue("term") - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) - return - } else { - githubClient = p.githubConnect(*info.Token) - } - - result, _, err := githubClient.Search.Issues(ctx, getIssuesSearchQuery(config.GitHubOrg, searchTerm), &github.SearchOptions{}) + result, _, err := githubClient.Search.Issues(context.Background(), getIssuesSearchQuery(config.GitHubOrg, searchTerm), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return } - resp, _ := json.Marshal(result.Issues) - w.Write(resp) + p.writeJSON(w, result.Issues) } -func (p *Plugin) getPermaLink(postId string) string { - siteUrl := *p.API.GetConfig().ServiceSettings.SiteURL +func (p *Plugin) getPermaLink(postID string) string { + siteURL := *p.API.GetConfig().ServiceSettings.SiteURL - return fmt.Sprintf("%v/_redirect/pl/%v", siteUrl, postId) + return fmt.Sprintf("%v/_redirect/pl/%v", siteURL, postID) } func getFailReason(code int, repo string, username string) string { @@ -678,75 +674,72 @@ func getFailReason(code int, repo string, username string) string { } func (p *Plugin) createIssueComment(w http.ResponseWriter, r *http.Request, userID string) { + type CreateIssueCommentRequest struct { + PostID string `json:"post_id"` + Owner string `json:"owner"` + Repo string `json:"repo"` + Number int `json:"number"` + Comment string `json:"comment"` + } + req := &CreateIssueCommentRequest{} if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - mlog.Error("Error decoding JSON body", mlog.Err(err)) - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + mlog.Error("Error decoding CreateIssueCommentRequest JSON body", mlog.Err(err)) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) return } - if req.PostId == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) + if req.PostID == "" { + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid post id", StatusCode: http.StatusBadRequest}) return } if req.Owner == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo owner.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo owner.", StatusCode: http.StatusBadRequest}) return } if req.Repo == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo.", StatusCode: http.StatusBadRequest}) return } if req.Number == 0 { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue number.", StatusCode: http.StatusBadRequest}) return } if req.Comment == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid non empty comment.", StatusCode: http.StatusBadRequest}) return } - ctx := context.Background() - - var githubClient *github.Client - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) } - api := p.API - post, appErr := api.GetPost(req.PostId) + githubClient := p.githubConnect(*info.Token) + + post, appErr := p.API.GetPost(req.PostID) if appErr != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + req.PostId, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + req.PostID, StatusCode: http.StatusInternalServerError}) return } if post == nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + req.PostId + ": not found", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + req.PostID + ": not found", StatusCode: http.StatusNotFound}) return } commentUsername, err := p.getUsername(post.UserId) if err != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } - currentUsername := "" - if info, apiEr := p.getGitHubUserInfo(userID); apiEr != nil { - writeAPIError(w, apiEr) - return - } else { - currentUsername = info.GitHubUsername - } - - permalink := p.getPermaLink(req.PostId) + currentUsername := info.GitHubUsername + permalink := p.getPermaLink(req.PostID) permalinkMessage := fmt.Sprintf("*@%s attached a* [message](%s) *from %s*\n\n", currentUsername, permalink, commentUsername) req.Comment = permalinkMessage + req.Comment @@ -754,89 +747,91 @@ func (p *Plugin) createIssueComment(w http.ResponseWriter, r *http.Request, user Body: &req.Comment, } - result, rawResponse, err := githubClient.Issues.CreateComment(ctx, req.Owner, req.Repo, req.Number, comment) + result, rawResponse, err := githubClient.Issues.CreateComment(context.Background(), req.Owner, req.Repo, req.Number, comment) if err != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create an issue comment: " + getFailReason(rawResponse.StatusCode, req.Repo, currentUsername), StatusCode: rawResponse.StatusCode}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create an issue comment: " + getFailReason(rawResponse.StatusCode, req.Repo, currentUsername), StatusCode: rawResponse.StatusCode}) return } - rootId := req.PostId + + rootID := req.PostID if post.RootId != "" { // the original post was a reply - rootId = post.RootId + rootID = post.RootId } permalinkReplyMessage := fmt.Sprintf("[Message](%v) attached to GitHub issue [#%v](%v)", permalink, req.Number, result.GetHTMLURL()) reply := &model.Post{ Message: permalinkReplyMessage, ChannelId: post.ChannelId, - RootId: rootId, - ParentId: rootId, + RootId: rootID, + ParentId: rootID, UserId: userID, } - _, appErr = api.CreatePost(reply) + _, appErr = p.API.CreatePost(reply) if appErr != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post " + req.PostId, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post " + req.PostID, StatusCode: http.StatusInternalServerError}) return } - resp, _ := json.Marshal(result) - w.Write(resp) + + p.writeJSON(w, result) } func (p *Plugin) getYourAssignments(w http.ResponseWriter, r *http.Request, userID string) { config := p.getConfiguration() - ctx := context.Background() - - var githubClient *github.Client - username := "" - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) - username = info.GitHubUsername } - result, _, err := githubClient.Search.Issues(ctx, getYourAssigneeSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) + githubClient := p.githubConnect(*info.Token) + username := info.GitHubUsername + + result, _, err := githubClient.Search.Issues(context.Background(), getYourAssigneeSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return } - resp, _ := json.Marshal(result.Issues) - w.Write(resp) + p.writeJSON(w, result.Issues) } func (p *Plugin) postToDo(w http.ResponseWriter, r *http.Request, userID string) { - var githubClient *github.Client - username := "" - - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) - username = info.GitHubUsername } + githubClient := p.githubConnect(*info.Token) + username := info.GitHubUsername + text, err := p.GetToDo(context.Background(), username, githubClient) if err != nil { mlog.Error(err.Error()) - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Encountered an error getting the to do items.", StatusCode: http.StatusUnauthorized}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Encountered an error getting the to do items.", StatusCode: http.StatusUnauthorized}) return } - if err := p.CreateBotDMPost(userID, text, "custom_git_todo"); err != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Encountered an error posting the to do items.", StatusCode: http.StatusUnauthorized}) - } + p.CreateBotDMPost(userID, text, "custom_git_todo") - w.Write([]byte("{\"status\": \"OK\"}")) + resp := struct { + Status string + }{"OK"} + + p.writeJSON(w, resp) } func (p *Plugin) updateSettings(w http.ResponseWriter, r *http.Request, userID string) { var settings *UserSettings - json.NewDecoder(r.Body).Decode(&settings) + if err := json.NewDecoder(r.Body).Decode(&settings); err != nil { + mlog.Error("Error decoding settings from JSON body", mlog.Err(err)) + http.Error(w, "Invalid request body", http.StatusBadRequest) + return + } + if settings == nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return @@ -844,7 +839,7 @@ func (p *Plugin) updateSettings(w http.ResponseWriter, r *http.Request, userID s info, err := p.getGitHubUserInfo(userID) if err != nil { - writeAPIError(w, err) + p.writeAPIError(w, err) return } @@ -853,69 +848,71 @@ func (p *Plugin) updateSettings(w http.ResponseWriter, r *http.Request, userID s if err := p.storeGitHubUserInfo(info); err != nil { mlog.Error(err.Error()) http.Error(w, "Encountered error updating settings", http.StatusInternalServerError) + return } - resp, _ := json.Marshal(info.Settings) - w.Write(resp) + p.writeJSON(w, info.Settings) } func (p *Plugin) getIssueByNumber(w http.ResponseWriter, r *http.Request, userID string) { - ctx := context.Background() - - var githubClient *github.Client owner := r.FormValue("owner") repo := r.FormValue("repo") number := r.FormValue("number") - numberInt, _ := strconv.Atoi(number) + numberInt, err := strconv.Atoi(number) + if err != nil { + p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + return + } - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) } + githubClient := p.githubConnect(*info.Token) - result, _, err := githubClient.Issues.Get(ctx, owner, repo, numberInt) + result, _, err := githubClient.Issues.Get(context.Background(), owner, repo, numberInt) if err != nil { mlog.Error(err.Error()) + p.writeAPIError(w, &APIErrorResponse{Message: "Could get issue.", StatusCode: http.StatusInternalServerError}) + return } - resp, _ := json.Marshal(result) - w.Write(resp) + p.writeJSON(w, result) } func (p *Plugin) getPrByNumber(w http.ResponseWriter, r *http.Request, userID string) { - ctx := context.Background() - - var githubClient *github.Client owner := r.FormValue("owner") repo := r.FormValue("repo") number := r.FormValue("number") numberInt, err := strconv.Atoi(number) if err != nil { - mlog.Error(err.Error()) + p.writeAPIError(w, &APIErrorResponse{Message: "Invalid param 'number'.", StatusCode: http.StatusBadRequest}) + return } - if info, err := p.getGitHubUserInfo(userID); err != nil { - writeAPIError(w, err) + info, apiErr := p.getGitHubUserInfo(userID) + if apiErr != nil { + p.writeAPIError(w, apiErr) return - } else { - githubClient = p.githubConnect(*info.Token) } + githubClient := p.githubConnect(*info.Token) - result, _, err := githubClient.PullRequests.Get(ctx, owner, repo, numberInt) + result, _, err := githubClient.PullRequests.Get(context.Background(), owner, repo, numberInt) if err != nil { mlog.Error(err.Error()) + p.writeAPIError(w, &APIErrorResponse{Message: "Could get pull request.", StatusCode: http.StatusInternalServerError}) + return } - resp, _ := json.Marshal(result) - w.Write(resp) + p.writeJSON(w, result) } + func (p *Plugin) getRepositories(w http.ResponseWriter, r *http.Request, userID string) { info, err := p.getGitHubUserInfo(userID) if err != nil { - writeAPIError(w, err) + p.writeAPIError(w, err) return } @@ -932,6 +929,7 @@ func (p *Plugin) getRepositories(w http.ResponseWriter, r *http.Request, userID repos, resp, err := githubClient.Repositories.List(ctx, "", &github.RepositoryListOptions{ListOptions: opt}) if err != nil { mlog.Error(err.Error()) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } allRepos = append(allRepos, repos...) @@ -945,6 +943,7 @@ func (p *Plugin) getRepositories(w http.ResponseWriter, r *http.Request, userID repos, resp, err := githubClient.Repositories.ListByOrg(ctx, org, &github.RepositoryListByOrgOptions{Sort: "full_name", ListOptions: opt}) if err != nil { mlog.Error(err.Error()) + p.writeAPIError(w, &APIErrorResponse{Message: "Failed to fetch repositories", StatusCode: http.StatusInternalServerError}) return } allRepos = append(allRepos, repos...) @@ -961,14 +960,13 @@ func (p *Plugin) getRepositories(w http.ResponseWriter, r *http.Request, userID FullName string `json:"full_name,omitempty"` } - response := make([]RepositoryResponse, len(allRepos)) + resp := make([]RepositoryResponse, len(allRepos)) for i, r := range allRepos { - response[i].Name = r.GetName() - response[i].FullName = r.GetFullName() + resp[i].Name = r.GetName() + resp[i].FullName = r.GetFullName() } - resp, _ := json.Marshal(response) - w.Write(resp) + p.writeJSON(w, resp) } func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID string) { @@ -976,52 +974,52 @@ func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID stri Title string `json:"title"` Body string `json:"body"` Repo string `json:"repo"` - PostId string `json:"post_id"` + PostID string `json:"post_id"` } // get data for the issue from the request body and fill IssueRequest object issue := &IssueRequest{} if err := json.NewDecoder(r.Body).Decode(&issue); err != nil { mlog.Error("Error decoding JSON body", mlog.Err(err)) - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a JSON object.", StatusCode: http.StatusBadRequest}) return } if issue.Title == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid issue title.", StatusCode: http.StatusBadRequest}) return } if issue.Repo == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo name.", StatusCode: http.StatusBadRequest}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a valid repo name.", StatusCode: http.StatusBadRequest}) return } - if issue.PostId == "" { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a postID", StatusCode: http.StatusBadRequest}) + if issue.PostID == "" { + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Please provide a postID", StatusCode: http.StatusBadRequest}) return } // Make sure user has a connected github account info, apiErr := p.getGitHubUserInfo(userID) if apiErr != nil { - writeAPIError(w, apiErr) + p.writeAPIError(w, apiErr) return } - post, appErr := p.API.GetPost(issue.PostId) + post, appErr := p.API.GetPost(issue.PostID) if appErr != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + issue.PostId, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + issue.PostID, StatusCode: http.StatusInternalServerError}) return } if post == nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + issue.PostId + ": not found", StatusCode: http.StatusNotFound}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load post " + issue.PostID + ": not found", StatusCode: http.StatusNotFound}) return } username, err := p.getUsername(post.UserId) if err != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to get username", StatusCode: http.StatusInternalServerError}) return } @@ -1030,7 +1028,7 @@ func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID stri Body: &issue.Body, } - permalink := p.getPermaLink(issue.PostId) + permalink := p.getPermaLink(issue.PostID) mmMessage := fmt.Sprintf("_Issue created from a [Mattermost message](%v) *by %s*._", permalink, username) @@ -1041,7 +1039,7 @@ func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID stri currentUser, appErr := p.API.GetUser(userID) if appErr != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to load current user", StatusCode: http.StatusInternalServerError}) return } @@ -1052,7 +1050,7 @@ func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID stri githubClient := p.githubConnect(*info.Token) result, resp, err := githubClient.Issues.Create(context.Background(), owner, repoName, ghIssue) if err != nil { - writeAPIError(w, + p.writeAPIError(w, &APIErrorResponse{ ID: "", Message: "failed to create issue: " + getFailReason(resp.StatusCode, @@ -1064,30 +1062,29 @@ func (p *Plugin) createIssue(w http.ResponseWriter, r *http.Request, userID stri } if resp.Response.StatusCode == http.StatusGone { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "Issues are disabled on this repository.", StatusCode: http.StatusMethodNotAllowed}) return } - rootId := issue.PostId + rootID := issue.PostID if post.RootId != "" { - rootId = post.RootId + rootID = post.RootId } message := fmt.Sprintf("Created GitHub issue [#%v](%v) from a [message](%s)", result.GetNumber(), result.GetHTMLURL(), permalink) reply := &model.Post{ Message: message, ChannelId: post.ChannelId, - RootId: rootId, - ParentId: rootId, + RootId: rootID, + ParentId: rootID, UserId: userID, } _, appErr = p.API.CreatePost(reply) if appErr != nil { - writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post " + issue.PostId, StatusCode: http.StatusInternalServerError}) + p.writeAPIError(w, &APIErrorResponse{ID: "", Message: "failed to create notification post " + issue.PostID, StatusCode: http.StatusInternalServerError}) return } - json_r, _ := json.Marshal(result) - w.Write(json_r) + p.writeJSON(w, result) } diff --git a/server/api_test.go b/server/api_test.go index e1d1f0deb..5588c8f9e 100644 --- a/server/api_test.go +++ b/server/api_test.go @@ -1,14 +1,15 @@ package main import ( - "github.com/mattermost/mattermost-plugin-github/server/testutils" "net/http" "net/http/httptest" "testing" + + "github.com/mattermost/mattermost-plugin-github/server/testutils" ) func TestPlugin_ServeHTTP(t *testing.T) { - httpTestJson := testutils.HTTPTest{ + httpTestJSON := testutils.HTTPTest{ T: t, Encoder: testutils.EncodeJSON, } @@ -27,9 +28,9 @@ func TestPlugin_ServeHTTP(t *testing.T) { }{ { name: "unauthorized test json", - httpTest: httpTestJson, + httpTest: httpTestJSON, request: testutils.Request{ - Method: "GET", + Method: http.MethodGet, URL: "/api/v1/connected", Body: nil, }, @@ -44,7 +45,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { name: "unauthorized test http", httpTest: httpTestString, request: testutils.Request{ - Method: "GET", + Method: http.MethodGet, URL: "/api/v1/reviews", Body: nil, }, @@ -71,7 +72,7 @@ func TestPlugin_ServeHTTP(t *testing.T) { EnterpriseUploadURL: "", EnableCodePreview: false, }) - p.initialiseAPI() + p.initializeAPI() req := tt.httpTest.CreateHTTPRequest(tt.request) req.Header.Add("Mattermost-User-ID", tt.userID) diff --git a/server/command.go b/server/command.go index 5a1b20673..9cfdec99b 100644 --- a/server/command.go +++ b/server/command.go @@ -5,14 +5,13 @@ import ( "fmt" "strings" - "github.com/mattermost/mattermost-server/v5/mlog" - "github.com/mattermost/mattermost-server/v5/plugin" - "github.com/google/go-github/v31/github" + "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" + "github.com/mattermost/mattermost-server/v5/plugin" ) -const COMMAND_HELP = `* |/github connect [private]| - Connect your Mattermost account to your GitHub account. +const commandHelp = `* |/github connect [private]| - Connect your Mattermost account to your GitHub account. * |private| is optional. If used, the github bot will ask for read access to your private repositories. If these repositories send webhook events to this Mattermost server, you will be notified of changes to those repositories. * |/github disconnect| - Disconnect your Mattermost account from your GitHub account * |/github todo| - Get a list of unread messages and pull requests awaiting your review @@ -37,14 +36,24 @@ const COMMAND_HELP = `* |/github connect [private]| - Connect your Mattermost ac * |setting| can be "notifications" or "reminders" * |value| can be "on" or "off"` +const ( + featureIssues = "issues" + featurePulls = "pulls" + featurePushes = "pushes" + featureCreates = "creates" + featureDeletes = "deletes" + featureIssueComments = "issue_comments" + featurePullReviews = "pull_reviews" +) + var validFeatures = map[string]bool{ - "issues": true, - "pulls": true, - "pushes": true, - "creates": true, - "deletes": true, - "issue_comments": true, - "pull_reviews": true, + featureIssues: true, + featurePulls: true, + featurePushes: true, + featureCreates: true, + featureDeletes: true, + featureIssueComments: true, + featurePullReviews: true, } // validateFeatures returns false when 1 or more given features @@ -67,7 +76,7 @@ func validateFeatures(features []string) (bool, []string) { if valid && hasLabel { // must have "pulls" or "issues" in features when using a label for _, f := range features { - if f == "pulls" || f == "issues" { + if f == featurePulls || f == featureIssues { return valid, invalidFeatures } } @@ -101,14 +110,14 @@ func (p *Plugin) getGithubClient(userInfo *GitHubUserInfo) *github.Client { } func (p *Plugin) handleSubscribe(_ *plugin.Context, args *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { - config := p.getConfiguration() features := "pulls,issues,creates,deletes" flags := SubscriptionFlags{} txt := "" - if len(parameters) == 0 { + switch { + case len(parameters) == 0: return "Please specify a repository or 'list' command." - } else if len(parameters) == 1 && parameters[0] == "list" { + case len(parameters) == 1 && parameters[0] == "list": subs, err := p.GetSubscriptionsByChannel(args.ChannelId) if err != nil { return err.Error() @@ -128,7 +137,7 @@ func (p *Plugin) handleSubscribe(_ *plugin.Context, args *model.CommandArgs, par txt += "\n" } return txt - } else if len(parameters) > 1 { + case len(parameters) > 1: var optionList []string for _, element := range parameters[1:] { @@ -158,7 +167,7 @@ func (p *Plugin) handleSubscribe(_ *plugin.Context, args *model.CommandArgs, par ctx := context.Background() githubClient := p.getGithubClient(userInfo) - owner, repo := parseOwnerAndRepo(parameters[0], config.EnterpriseBaseURL) + owner, repo := parseOwnerAndRepo(parameters[0], p.getBaseURL()) if repo == "" { if err := p.SubscribeOrg(ctx, githubClient, args.UserId, owner, args.ChannelId, features, flags); err != nil { return err.Error() @@ -185,17 +194,16 @@ func (p *Plugin) handleUnsubscribe(_ *plugin.Context, args *model.CommandArgs, p return "Encountered an error trying to unsubscribe. Please try again." } - return fmt.Sprintf("Succesfully unsubscribed from %s.", repo) + return fmt.Sprintf("Successfully unsubscribed from %s.", repo) } func (p *Plugin) handleDisconnect(_ *plugin.Context, args *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { p.disconnectGitHubAccount(args.UserId) return "Disconnected your GitHub account." } func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *GitHubUserInfo) string { - ctx := context.Background() githubClient := p.getGithubClient(userInfo) - text, err := p.GetToDo(ctx, userInfo.GitHubUsername, githubClient) + text, err := p.GetToDo(context.Background(), userInfo.GitHubUsername, githubClient) if err != nil { mlog.Error(err.Error()) return "Encountered an error getting your to do items." @@ -203,9 +211,8 @@ func (p *Plugin) handleTodo(_ *plugin.Context, _ *model.CommandArgs, _ []string, return text } func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, userInfo *GitHubUserInfo) string { - ctx := context.Background() githubClient := p.getGithubClient(userInfo) - gitUser, _, err := githubClient.Users.Get(ctx, "") + gitUser, _, err := githubClient.Users.Get(context.Background(), "") if err != nil { return "Encountered an error getting your GitHub profile." } @@ -214,11 +221,11 @@ func (p *Plugin) handleMe(_ *plugin.Context, _ *model.CommandArgs, _ []string, u return text } func (p *Plugin) handleHelp(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { - text := "###### Mattermost GitHub Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) + text := "###### Mattermost GitHub Plugin - Slash Command Help\n" + strings.Replace(commandHelp, "|", "`", -1) return text } func (p *Plugin) handleEmpty(_ *plugin.Context, _ *model.CommandArgs, _ []string, _ *GitHubUserInfo) string { - text := "###### Mattermost GitHub Plugin - Slash Command Help\n" + strings.Replace(COMMAND_HELP, "|", "`", -1) + text := "###### Mattermost GitHub Plugin - Slash Command Help\n" + strings.Replace(commandHelp, "|", "`", -1) return text } func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, parameters []string, userInfo *GitHubUserInfo) string { @@ -227,39 +234,40 @@ func (p *Plugin) handleSettings(_ *plugin.Context, _ *model.CommandArgs, paramet } setting := parameters[0] - if setting != SETTING_NOTIFICATIONS && setting != SETTING_REMINDERS { + if setting != settingNotifications && setting != settingReminders { return "Unknown setting." } strValue := parameters[1] value := false - if strValue == SETTING_ON { + if strValue == settingOn { value = true - } else if strValue != SETTING_OFF { + } else if strValue != settingOff { return "Invalid value. Accepted values are: \"on\" or \"off\"." } - if setting == SETTING_NOTIFICATIONS { + if setting == settingNotifications { if value { err := p.storeGitHubToUserIDMapping(userInfo.GitHubUsername, userInfo.UserID) if err != nil { mlog.Error(err.Error()) } } else { - err := p.API.KVDelete(userInfo.GitHubUsername + GITHUB_USERNAME_KEY) + err := p.API.KVDelete(userInfo.GitHubUsername + githubUsernameKey) if err != nil { mlog.Error(err.Error()) } } userInfo.Settings.Notifications = value - } else if setting == SETTING_REMINDERS { + } else if setting == settingReminders { userInfo.Settings.DailyReminder = value } err := p.storeGitHubUserInfo(userInfo) if err != nil { mlog.Error(err.Error()) + return "Failed to store settings" } return "Settings updated." @@ -302,7 +310,7 @@ func (p *Plugin) ExecuteCommand(c *plugin.Context, args *model.CommandArgs) (*mo info, apiErr := p.getGitHubUserInfo(args.UserId) if apiErr != nil { text := "Unknown error." - if apiErr.ID == API_ERROR_ID_NOT_CONNECTED { + if apiErr.ID == apiErrorIDNotConnected { text = "You must connect your account to GitHub first. Either click on the GitHub logo in the bottom left of the screen or enter `/github connect`." } p.postCommandResponse(args, text) diff --git a/server/configuration.go b/server/configuration.go index e456f1f4c..c92b29d92 100644 --- a/server/configuration.go +++ b/server/configuration.go @@ -39,15 +39,15 @@ func (c *configuration) Clone() *configuration { // IsValid checks if all needed fields are set. func (c *configuration) IsValid() error { if c.GitHubOAuthClientID == "" { - return errors.New("Must have a github oauth client id") + return errors.New("must have a github oauth client id") } if c.GitHubOAuthClientSecret == "" { - return errors.New("Must have a github oauth client secret") + return errors.New("must have a github oauth client secret") } if c.EncryptionKey == "" { - return errors.New("Must have an encryption key") + return errors.New("must have an encryption key") } return nil diff --git a/server/manifest.go b/server/manifest.go index 343af0678..4c36dc1a4 100644 --- a/server/manifest.go +++ b/server/manifest.go @@ -1,9 +1,11 @@ +// This file is automatically generated. Do not modify it manually. + package main var manifest = struct { - Id string + ID string Version string }{ - Id: "github", + ID: "github", Version: "0.14.0", } diff --git a/server/permalinks_test.go b/server/permalinks_test.go index eda9e50d8..3d4f325fb 100644 --- a/server/permalinks_test.go +++ b/server/permalinks_test.go @@ -8,11 +8,10 @@ import ( "testing" "github.com/google/go-github/v31/github" + "github.com/mattermost/mattermost-server/v5/plugin/plugintest" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" - - "github.com/mattermost/mattermost-server/v5/plugin/plugintest" ) func TestGetReplacements(t *testing.T) { diff --git a/server/plugin.go b/server/plugin.go index 773aa25f3..8eb90c1bb 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -23,17 +23,21 @@ import ( ) const ( - GITHUB_TOKEN_KEY = "_githubtoken" - GITHUB_USERNAME_KEY = "_githubusername" - GITHUB_PRIVATE_REPO_KEY = "_githubprivate" - WS_EVENT_CONNECT = "connect" - WS_EVENT_DISCONNECT = "disconnect" - WS_EVENT_REFRESH = "refresh" - SETTING_BUTTONS_TEAM = "team" - SETTING_NOTIFICATIONS = "notifications" - SETTING_REMINDERS = "reminders" - SETTING_ON = "on" - SETTING_OFF = "off" + githubTokenKey = "_githubtoken" + githubUsernameKey = "_githubusername" + githubPrivateRepoKey = "_githubprivate" + + wsEventConnect = "connect" + wsEventDisconnect = "disconnect" + wsEventRefresh = "refresh" + + settingButtonsTeam = "team" + settingNotifications = "notifications" + settingReminders = "reminders" + settingOn = "on" + settingOff = "off" + + notificationReasonSubscribed = "subscribed" ) type Plugin struct { @@ -78,9 +82,8 @@ func NewPlugin() *Plugin { func (p *Plugin) githubConnect(token oauth2.Token) *github.Client { config := p.getConfiguration() - ctx := context.Background() ts := oauth2.StaticTokenSource(&token) - tc := oauth2.NewClient(ctx, ts) + tc := oauth2.NewClient(context.Background(), ts) if len(config.EnterpriseBaseURL) == 0 || len(config.EnterpriseUploadURL) == 0 { return github.NewClient(tc) @@ -111,14 +114,14 @@ func (p *Plugin) OnActivate() error { return errors.New("siteURL is not set. Please set a siteURL and restart the plugin") } - p.initialiseAPI() + p.initializeAPI() err := p.API.RegisterCommand(getCommand()) if err != nil { return errors.Wrap(err, "failed to register command") } - botId, err := p.Helpers.EnsureBot(&model.Bot{ + botID, err := p.Helpers.EnsureBot(&model.Bot{ Username: "github", DisplayName: "GitHub", Description: "Created by the GitHub plugin.", @@ -126,7 +129,7 @@ func (p *Plugin) OnActivate() error { if err != nil { return errors.Wrap(err, "failed to ensure github bot") } - p.BotUserID = botId + p.BotUserID = botID bundlePath, err := p.API.GetBundlePath() if err != nil { @@ -138,7 +141,7 @@ func (p *Plugin) OnActivate() error { return errors.Wrap(err, "couldn't read profile image") } - appErr := p.API.SetProfileImage(botId, profileImage) + appErr := p.API.SetProfileImage(botID, profileImage) if appErr != nil { return errors.Wrap(appErr, "couldn't set profile image") } @@ -176,12 +179,9 @@ func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*mode func (p *Plugin) getOAuthConfig(privateAllowed bool) *oauth2.Config { config := p.getConfiguration() - authURL, _ := url.Parse("https://github.com/") - tokenURL, _ := url.Parse("https://github.com/") - if len(config.EnterpriseBaseURL) > 0 { - authURL, _ = url.Parse(config.EnterpriseBaseURL) - tokenURL, _ = url.Parse(config.EnterpriseBaseURL) - } + baseURL := p.getBaseURL() + authURL, _ := url.Parse(baseURL) + tokenURL, _ := url.Parse(baseURL) authURL.Path = path.Join(authURL.Path, "login", "oauth", "authorize") tokenURL.Path = path.Join(tokenURL.Path, "login", "oauth", "access_token") @@ -233,8 +233,8 @@ func (p *Plugin) storeGitHubUserInfo(info *GitHubUserInfo) error { return errors.Wrap(err, "error while converting user info to json") } - if err := p.API.KVSet(info.UserID+GITHUB_TOKEN_KEY, jsonInfo); err != nil { - return errors.Wrap(err, "error occurred while trying to store user info into KVStore") + if err := p.API.KVSet(info.UserID+githubTokenKey, jsonInfo); err != nil { + return errors.Wrap(err, "error occurred while trying to store user info into KV store") } return nil @@ -245,9 +245,12 @@ func (p *Plugin) getGitHubUserInfo(userID string) (*GitHubUserInfo, *APIErrorRes var userInfo GitHubUserInfo - if infoBytes, err := p.API.KVGet(userID + GITHUB_TOKEN_KEY); err != nil || infoBytes == nil { - return nil, &APIErrorResponse{ID: API_ERROR_ID_NOT_CONNECTED, Message: "Must connect user account to GitHub first.", StatusCode: http.StatusBadRequest} - } else if err := json.Unmarshal(infoBytes, &userInfo); err != nil { + infoBytes, appErr := p.API.KVGet(userID + githubTokenKey) + if appErr != nil || infoBytes == nil { + return nil, &APIErrorResponse{ID: apiErrorIDNotConnected, Message: "Must connect user account to GitHub first.", StatusCode: http.StatusBadRequest} + } + + if err := json.Unmarshal(infoBytes, &userInfo); err != nil { return nil, &APIErrorResponse{ID: "", Message: "Unable to parse token.", StatusCode: http.StatusInternalServerError} } @@ -263,14 +266,14 @@ func (p *Plugin) getGitHubUserInfo(userID string) (*GitHubUserInfo, *APIErrorRes } func (p *Plugin) storeGitHubToUserIDMapping(githubUsername, userID string) error { - if err := p.API.KVSet(githubUsername+GITHUB_USERNAME_KEY, []byte(userID)); err != nil { - return errors.New("Encountered error saving github username mapping") + if err := p.API.KVSet(githubUsername+githubUsernameKey, []byte(userID)); err != nil { + return errors.New("encountered error saving github username mapping") } return nil } func (p *Plugin) getGitHubToUserIDMapping(githubUsername string) string { - userID, _ := p.API.KVGet(githubUsername + GITHUB_USERNAME_KEY) + userID, _ := p.API.KVGet(githubUsername + githubUsernameKey) return string(userID) } @@ -290,33 +293,42 @@ func (p *Plugin) disconnectGitHubAccount(userID string) { return } - if appErr := p.API.KVDelete(userID + GITHUB_TOKEN_KEY); appErr != nil { - mlog.Error("Could not delete userid from kvstore") + if appErr := p.API.KVDelete(userID + githubTokenKey); appErr != nil { + p.API.LogWarn("Failed to delete github token from KV store", "userID", userID, "error", appErr.Error()) } - if appErr := p.API.KVDelete(userInfo.GitHubUsername + GITHUB_USERNAME_KEY); appErr != nil { - mlog.Error("Could not delete user info from kvstore") + if appErr := p.API.KVDelete(userInfo.GitHubUsername + githubUsernameKey); appErr != nil { + p.API.LogWarn("Failed to delete github token from KV store", "userID", userID, "error", appErr.Error()) } - if user, err := p.API.GetUser(userID); err == nil && user.Props != nil && len(user.Props["git_user"]) > 0 { - delete(user.Props, "git_user") - if _, appErr := p.API.UpdateUser(user); appErr != nil { - mlog.Error("Could not delete user info from kvstore") + user, appErr := p.API.GetUser(userID) + if appErr != nil { + p.API.LogWarn("Failed to get user props", "userID", userID, "error", appErr.Error()) + } else { + _, ok := user.Props["git_user"] + if ok { + delete(user.Props, "git_user") + _, appErr := p.API.UpdateUser(user) + if appErr != nil { + p.API.LogWarn("Failed to get update user props", "userID", userID, "error", appErr.Error()) + } } } p.API.PublishWebSocketEvent( - WS_EVENT_DISCONNECT, + wsEventDisconnect, nil, &model.WebsocketBroadcast{UserId: userID}, ) } -func (p *Plugin) CreateBotDMPost(userID, message, postType string) *model.AppError { +// CreateBotDMPost posts a direct message using the bot account. +// Any error are not returned and instead logged. +func (p *Plugin) CreateBotDMPost(userID, message, postType string) { channel, err := p.API.GetDirectChannel(userID, p.BotUserID) if err != nil { - mlog.Error("Couldn't get bot's DM channel", mlog.String("user_id", userID)) - return err + p.API.LogWarn("Couldn't get bot's DM channel", "userID", userID, "error", err.Error()) + return } post := &model.Post{ @@ -327,25 +339,24 @@ func (p *Plugin) CreateBotDMPost(userID, message, postType string) *model.AppErr } if _, err := p.API.CreatePost(post); err != nil { - mlog.Error(err.Error()) - return err + p.API.LogWarn("Failed to create DM post", "userID", userID, "error", err.Error()) + return } - - return nil } func (p *Plugin) PostToDo(info *GitHubUserInfo) { text, err := p.GetToDo(context.Background(), info.GitHubUsername, p.githubConnect(*info.Token)) if err != nil { - mlog.Error(err.Error()) + p.API.LogWarn("Failed to get todo text", "userID", info.UserID, "error", err.Error()) return } - _ = p.CreateBotDMPost(info.UserID, text, "custom_git_todo") + p.CreateBotDMPost(info.UserID, text, "custom_git_todo") } func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *github.Client) (string, error) { config := p.getConfiguration() + baseURL := p.getBaseURL() issueResults, _, err := githubClient.Search.Issues(ctx, getReviewSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { @@ -372,7 +383,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git notificationCount := 0 notificationContent := "" for _, n := range notifications { - if n.GetReason() == "subscribed" { + if n.GetReason() == notificationReasonSubscribed { continue } @@ -394,7 +405,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git default: notificationTitle := notificationSubject.GetTitle() notificationURL := fixGithubNotificationSubjectURL(notificationSubject.GetURL()) - notificationContent += getToDoDisplayText(notificationTitle, notificationURL, notificationType) + notificationContent += getToDoDisplayText(baseURL, notificationTitle, notificationURL, notificationType) } notificationCount++ @@ -415,7 +426,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git text += fmt.Sprintf("You have %v pull requests awaiting your review:\n", issueResults.GetTotal()) for _, pr := range issueResults.Issues { - text += getToDoDisplayText(pr.GetTitle(), pr.GetHTMLURL(), "") + text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "") } } @@ -427,7 +438,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git text += fmt.Sprintf("You have %v open pull requests:\n", yourPrs.GetTotal()) for _, pr := range yourPrs.Issues { - text += getToDoDisplayText(pr.GetTitle(), pr.GetHTMLURL(), "") + text += getToDoDisplayText(baseURL, pr.GetTitle(), pr.GetHTMLURL(), "") } } @@ -439,7 +450,7 @@ func (p *Plugin) GetToDo(ctx context.Context, username string, githubClient *git text += fmt.Sprintf("You have %v assignments:\n", yourAssignments.GetTotal()) for _, assign := range yourAssignments.Issues { - text += getToDoDisplayText(assign.GetTitle(), assign.GetHTMLURL(), "") + text += getToDoDisplayText(baseURL, assign.GetTitle(), assign.GetHTMLURL(), "") } } @@ -455,26 +466,30 @@ func (p *Plugin) HasUnreads(info *GitHubUserInfo) bool { issues, _, err := githubClient.Search.Issues(ctx, getReviewSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return false } yourPrs, _, err := githubClient.Search.Issues(ctx, getYourPrsSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return false } yourAssignments, _, err := githubClient.Search.Issues(ctx, getYourAssigneeSearchQuery(username, config.GitHubOrg), &github.SearchOptions{}) if err != nil { mlog.Error(err.Error()) + return false } relevantNotifications := false notifications, _, err := githubClient.Activity.ListNotifications(ctx, &github.NotificationListOptions{}) if err != nil { mlog.Error(err.Error()) + return false } for _, n := range notifications { - if n.GetReason() == "subscribed" { + if n.GetReason() == notificationReasonSubscribed { continue } @@ -503,7 +518,7 @@ func (p *Plugin) checkOrg(org string) error { configOrg := strings.TrimSpace(config.GitHubOrg) if configOrg != "" && configOrg != org { - return errors.Errorf("Only repositories in the %v organization are supported", configOrg) + return errors.Errorf("only repositories in the %v organization are supported", configOrg) } return nil @@ -525,27 +540,38 @@ func (p *Plugin) isUserOrganizationMember(githubClient *github.Client, user *git func (p *Plugin) sendRefreshEvent(userID string) { p.API.PublishWebSocketEvent( - WS_EVENT_REFRESH, + wsEventRefresh, nil, &model.WebsocketBroadcast{UserId: userID}, ) } +func (p *Plugin) getBaseURL() string { + config := p.getConfiguration() + if config.EnterpriseBaseURL != "" { + return config.EnterpriseBaseURL + } + + return "https://github.com/" +} + // getUsername returns the GitHub username for a given Mattermost user, // if the user is connected to GitHub via this plugin. // Otherwise it return the Mattermost username. It will be escaped via backticks. func (p *Plugin) getUsername(mmUserID string) (string, error) { info, apiEr := p.getGitHubUserInfo(mmUserID) if apiEr != nil { - if apiEr.ID == API_ERROR_ID_NOT_CONNECTED { - user, appEr := p.API.GetUser(mmUserID) - if appEr != nil { - return "", appEr - } - return fmt.Sprintf("`@%s`", user.Username), nil - } else { + if apiEr.ID != apiErrorIDNotConnected { return "", apiEr } + + user, appEr := p.API.GetUser(mmUserID) + if appEr != nil { + return "", appEr + } + + return fmt.Sprintf("`@%s`", user.Username), nil } + return "@" + info.GitHubUsername, nil } diff --git a/server/subscriptions.go b/server/subscriptions.go index 40d943f7f..aec098e09 100644 --- a/server/subscriptions.go +++ b/server/subscriptions.go @@ -5,18 +5,17 @@ import ( "context" "encoding/json" "fmt" - "github.com/pkg/errors" "sort" "strings" - "github.com/mattermost/mattermost-server/v5/mlog" - "github.com/google/go-github/v31/github" + "github.com/mattermost/mattermost-server/v5/mlog" + "github.com/pkg/errors" ) const ( - SUBSCRIPTIONS_KEY = "subscriptions" - EXCLUDE_ORG_MEMBER_FLAG = "exclude-org-member" + SubscriptionsKey = "subscriptions" + excludeOrgMemberFlag = "exclude-org-member" ) type SubscriptionFlags struct { @@ -24,8 +23,8 @@ type SubscriptionFlags struct { } func (s *SubscriptionFlags) AddFlag(flag string) { - switch flag { - case EXCLUDE_ORG_MEMBER_FLAG: + switch flag { // nolint:gocritic // It's expected that more flags get added. + case excludeOrgMemberFlag: s.ExcludeOrgMembers = true } } @@ -34,7 +33,7 @@ func (s SubscriptionFlags) String() string { flags := []string{} if s.ExcludeOrgMembers { - flag := "--" + EXCLUDE_ORG_MEMBER_FLAG + flag := "--" + excludeOrgMemberFlag flags = append(flags, flag) } @@ -54,11 +53,11 @@ type Subscriptions struct { } func (s *Subscription) Pulls() bool { - return strings.Contains(s.Features, "pulls") + return strings.Contains(s.Features, featurePulls) } func (s *Subscription) Issues() bool { - return strings.Contains(s.Features, "issues") + return strings.Contains(s.Features, featureIssues) } func (s *Subscription) Pushes() bool { @@ -98,7 +97,7 @@ func (s *Subscription) ExcludeOrgMembers() bool { return s.Flags.ExcludeOrgMembers } -func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, userId, owner, repo, channelID, features string, flags SubscriptionFlags) error { +func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, userID, owner, repo, channelID, features string, flags SubscriptionFlags) error { if owner == "" { return errors.Errorf("invalid repository") } @@ -124,7 +123,7 @@ func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, use ghRepo, _, err = githubClient.Repositories.Get(ctx, owner, repo) if ghRepo == nil { - return errors.Errorf("Unknown repository %s", fullNameFromOwnerAndRepo(owner, repo)) + return errors.Errorf("unknown repository %s", fullNameFromOwnerAndRepo(owner, repo)) } } @@ -135,7 +134,7 @@ func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, use sub := &Subscription{ ChannelID: channelID, - CreatorID: userId, + CreatorID: userID, Features: features, Repository: fullNameFromOwnerAndRepo(owner, repo), Flags: flags, @@ -148,12 +147,12 @@ func (p *Plugin) Subscribe(ctx context.Context, githubClient *github.Client, use return nil } -func (p *Plugin) SubscribeOrg(ctx context.Context, githubClient *github.Client, userId, org, channelID, features string, flags SubscriptionFlags) error { +func (p *Plugin) SubscribeOrg(ctx context.Context, githubClient *github.Client, userID, org, channelID, features string, flags SubscriptionFlags) error { if org == "" { - return errors.New("Invalid organization") + return errors.New("invalid organization") } - return p.Subscribe(ctx, githubClient, userId, org, "", channelID, features, flags) + return p.Subscribe(ctx, githubClient, userID, org, "", channelID, features, flags) } func (p *Plugin) GetSubscriptionsByChannel(channelID string) ([]*Subscription, error) { @@ -219,18 +218,18 @@ func (p *Plugin) AddSubscription(repo string, sub *Subscription) error { func (p *Plugin) GetSubscriptions() (*Subscriptions, error) { var subscriptions *Subscriptions - value, err := p.API.KVGet(SUBSCRIPTIONS_KEY) - if err != nil { - return nil, errors.Wrap(err, "could not get subscriptions from KVStore") + value, appErr := p.API.KVGet(SubscriptionsKey) + if appErr != nil { + return nil, errors.Wrap(appErr, "could not get subscriptions from KVStore") } if value == nil { - subscriptions = &Subscriptions{Repositories: map[string][]*Subscription{}} - } else { - err := json.NewDecoder(bytes.NewReader(value)).Decode(&subscriptions) - if err != nil { - return nil, errors.Wrap(err, "could not properly decode subscriptions key") - } + return &Subscriptions{Repositories: map[string][]*Subscription{}}, nil + } + + err := json.NewDecoder(bytes.NewReader(value)).Decode(&subscriptions) + if err != nil { + return nil, errors.Wrap(err, "could not properly decode subscriptions key") } return subscriptions, nil @@ -242,9 +241,10 @@ func (p *Plugin) StoreSubscriptions(s *Subscriptions) error { return errors.Wrap(err, "error while converting subscriptions map to json") } - if appErr := p.API.KVSet(SUBSCRIPTIONS_KEY, b); appErr != nil { - return errors.Wrap(appErr, "could not store subscriptions in KVStore") + if appErr := p.API.KVSet(SubscriptionsKey, b); appErr != nil { + return errors.Wrap(appErr, "could not store subscriptions in KV store") } + return nil } @@ -285,11 +285,9 @@ func (p *Plugin) GetSubscribedChannelsForRepository(repo *github.Repository) []* } func (p *Plugin) Unsubscribe(channelID string, repo string) error { - config := p.getConfiguration() - - owner, repo := parseOwnerAndRepo(repo, config.EnterpriseBaseURL) + owner, repo := parseOwnerAndRepo(repo, p.getBaseURL()) if owner == "" && repo == "" { - return errors.New("Invalid repository") + return errors.New("invalid repository") } repoWithOwner := fmt.Sprintf("%s/%s", owner, repo) diff --git a/server/subscriptions_test.go b/server/subscriptions_test.go index a0c6839a5..094d99d50 100644 --- a/server/subscriptions_test.go +++ b/server/subscriptions_test.go @@ -2,9 +2,10 @@ package main import ( "encoding/json" + "testing" + "github.com/mattermost/mattermost-server/v5/plugin/plugintest" "github.com/stretchr/testify/assert" - "testing" ) func CheckError(t *testing.T, wantErr bool, err error) { @@ -23,7 +24,7 @@ func pluginWithMockedSubs(subscriptions []*Subscription) *Plugin { subs := Subscriptions{Repositories: map[string][]*Subscription{}} subs.Repositories[""] = subscriptions jsn, _ := json.Marshal(subs) - mockPluginAPI.On("KVGet", SUBSCRIPTIONS_KEY).Return(jsn, nil) + mockPluginAPI.On("KVGet", SubscriptionsKey).Return(jsn, nil) p.SetAPI(mockPluginAPI) return p } @@ -41,7 +42,6 @@ func wantedSubscriptions(repoNames []string, chanelID string) []*Subscription { } func TestPlugin_GetSubscriptionsByChannel(t *testing.T) { - type args struct { channelID string } diff --git a/server/template_test.go b/server/template_test.go index 0afe37f09..af2d93fdb 100644 --- a/server/template_test.go +++ b/server/template_test.go @@ -180,7 +180,6 @@ git-get-head gets the non-sent upstream heads inside the stashed non-cleaned app }) t.Run("with mentions", withGitHubUserNameMapping(func(t *testing.T) { - expected := ` #### Leverage git-get-head ##### [mattermost-plugin-github#42](https://github.com/mattermost/mattermost-plugin-github/pull/42) @@ -795,7 +794,6 @@ func TestCommentAuthorPullRequestNotificationTemplate(t *testing.T) { }) require.NoError(t, err) require.Equal(t, expected, actual) - }) t.Run("with mentions", withGitHubUserNameMapping(func(*testing.T) { @@ -816,7 +814,6 @@ func TestCommentAuthorPullRequestNotificationTemplate(t *testing.T) { }) require.NoError(t, err) require.Equal(t, expected, actual) - })) } diff --git a/server/testutils/http.go b/server/testutils/http.go index c6becb0ac..4e5141f38 100644 --- a/server/testutils/http.go +++ b/server/testutils/http.go @@ -3,12 +3,13 @@ package testutils import ( "bytes" "encoding/json" - "github.com/pkg/errors" - "github.com/stretchr/testify/assert" "io" "net/http" "net/http/httptest" "testing" + + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" ) const ( @@ -32,7 +33,7 @@ type Request struct { Body interface{} } -// ExpectedResponse stores expected responce basic data +// ExpectedResponse stores expected response basic data type ExpectedResponse struct { StatusCode int ResponseType contentType @@ -56,7 +57,6 @@ func EncodeJSON(data interface{}) ([]byte, error) { } return b, nil - } // EncodeJSON encodes json data in bytes diff --git a/server/utils.go b/server/utils.go index e3d4f8851..bf68289b4 100644 --- a/server/utils.go +++ b/server/utils.go @@ -8,12 +8,13 @@ import ( "crypto/rand" "encoding/base64" "fmt" - "github.com/pkg/errors" "io" "path" "strconv" "strings" "unicode" + + "github.com/pkg/errors" ) func getMentionSearchQuery(username, org string) string { @@ -99,7 +100,7 @@ func decrypt(key []byte, text string) (string, error) { } if (len(decodedMsg) % aes.BlockSize) != 0 { - return "", errors.New("blocksize must be multipe of decoded message length") + return "", errors.New("blocksize must be multiple of decoded message length") } iv := decodedMsg[:aes.BlockSize] @@ -117,9 +118,6 @@ func decrypt(key []byte, text string) (string, error) { } func parseOwnerAndRepo(full, baseURL string) (string, string) { - if baseURL == "" { - baseURL = "https://github.com/" - } full = strings.TrimSuffix(strings.TrimSpace(strings.Replace(full, baseURL, "", 1)), "/") splitStr := strings.Split(full, "/") @@ -259,7 +257,7 @@ func getLine(s string) int { return line } -// isInsideLink reports whether the given index in a string is preceeded +// isInsideLink reports whether the given index in a string is preceded // by zero or more space, then (, then ]. // // It is a poor man's version of checking markdown hyperlinks without @@ -313,9 +311,7 @@ func getCodeMarkdown(user, repo, repoPath, word, lines string, isTruncated bool) } // getToDoDisplayText returns the text to be displayed in todo listings. -func getToDoDisplayText(title, url, notifType string) string { - baseURL := "https://github.com/" - +func getToDoDisplayText(baseURL, title, url, notifType string) string { owner, repo := parseOwnerAndRepo(url, baseURL) repoURL := fmt.Sprintf("%s%s/%s", baseURL, owner, repo) repoWords := strings.Split(repo, "-") diff --git a/server/utils_test.go b/server/utils_test.go index b4e6d997c..be3421c16 100644 --- a/server/utils_test.go +++ b/server/utils_test.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "testing" "github.com/stretchr/testify/assert" @@ -60,22 +61,24 @@ func TestParseOwnerAndRepo(t *testing.T) { }{ {Full: "mattermost", BaseURL: "", ExpectedOwner: "mattermost", ExpectedRepo: ""}, {Full: "mattermost", BaseURL: "https://github.com/", ExpectedOwner: "mattermost", ExpectedRepo: ""}, - {Full: "https://github.com/mattermost", BaseURL: "", ExpectedOwner: "mattermost", ExpectedRepo: ""}, + {Full: "https://example.org/mattermost", BaseURL: "https://example.org/", ExpectedOwner: "mattermost", ExpectedRepo: ""}, {Full: "https://github.com/mattermost", BaseURL: "https://github.com/", ExpectedOwner: "mattermost", ExpectedRepo: ""}, {Full: "mattermost/mattermost-server", BaseURL: "", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost-server"}, {Full: "mattermost/mattermost-server", BaseURL: "https://github.com/", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost-server"}, - {Full: "https://github.com/mattermost/mattermost-server", BaseURL: "", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost-server"}, + {Full: "https://example.org/mattermost/mattermost-server", BaseURL: "https://example.org/", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost-server"}, {Full: "https://github.com/mattermost/mattermost-server", BaseURL: "https://github.com/", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost-server"}, {Full: "", BaseURL: "", ExpectedOwner: "", ExpectedRepo: ""}, {Full: "mattermost/mattermost/invalid_repo_url", BaseURL: "", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost"}, - {Full: "https://github.com/mattermost/mattermost/invalid_repo_url", BaseURL: "", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost"}, + {Full: "https://github.com/mattermost/mattermost/invalid_repo_url", BaseURL: "https://github.com/", ExpectedOwner: "mattermost", ExpectedRepo: "mattermost"}, } - for _, tc := range tcs { - owner, repo := parseOwnerAndRepo(tc.Full, tc.BaseURL) + for i, tc := range tcs { + t.Run(fmt.Sprintf("%v", i), func(t *testing.T) { + owner, repo := parseOwnerAndRepo(tc.Full, tc.BaseURL) - assert.Equal(t, owner, tc.ExpectedOwner) - assert.Equal(t, repo, tc.ExpectedRepo) + assert.Equal(t, tc.ExpectedOwner, owner) + assert.Equal(t, tc.ExpectedRepo, repo) + }) } } @@ -244,7 +247,7 @@ func TestGetToDoDisplayText(t *testing.T) { for _, tc := range tcs { t.Run(tc.name, func(t *testing.T) { - got := getToDoDisplayText(tc.in.title, tc.in.url, tc.in.notifType) + got := getToDoDisplayText("https://github.com/", tc.in.title, tc.in.url, tc.in.notifType) assert.Equal(t, tc.want, got) }) } diff --git a/server/webhook.go b/server/webhook.go index 7afa42412..213443db3 100644 --- a/server/webhook.go +++ b/server/webhook.go @@ -3,37 +3,48 @@ package main import ( "context" "crypto/hmac" - "crypto/sha1" + "crypto/sha1" //nolint:gosec // GitHub webhooks are signed using sha1 https://developer.github.com/webhooks/. "encoding/hex" "fmt" "io/ioutil" "net/http" "strings" + "github.com/google/go-github/v31/github" "github.com/mattermost/mattermost-server/v5/mlog" "github.com/mattermost/mattermost-server/v5/model" - - "github.com/google/go-github/v31/github" ) -func verifyWebhookSignature(secret []byte, signature string, body []byte) bool { +func verifyWebhookSignature(secret []byte, signature string, body []byte) (bool, error) { const signaturePrefix = "sha1=" const signatureLength = 45 if len(signature) != signatureLength || !strings.HasPrefix(signature, signaturePrefix) { - return false + return false, nil } actual := make([]byte, 20) - hex.Decode(actual, []byte(signature[5:])) + _, err := hex.Decode(actual, []byte(signature[5:])) + if err != nil { + return false, err + } + + sb, err := signBody(secret, body) + if err != nil { + return false, err + } - return hmac.Equal(signBody(secret, body), actual) + return hmac.Equal(sb, actual), nil } -func signBody(secret, body []byte) []byte { +func signBody(secret, body []byte) ([]byte, error) { computed := hmac.New(sha1.New, secret) - computed.Write(body) - return computed.Sum(nil) + _, err := computed.Write(body) + if err != nil { + return nil, err + } + + return computed.Sum(nil), nil } // Hack to convert from github.PushEventRepository to github.Repository @@ -57,7 +68,14 @@ func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) { return } - if !verifyWebhookSignature([]byte(config.WebhookSecret), signature, body) { + valid, err := verifyWebhookSignature([]byte(config.WebhookSecret), signature, body) + if err != nil { + p.API.LogWarn("Failed to verify webhook signature", "error", err.Error()) + http.Error(w, "", http.StatusInternalServerError) + return + } + + if !valid { http.Error(w, "Not authorized", http.StatusUnauthorized) return } @@ -136,9 +154,7 @@ func (p *Plugin) permissionToRepo(userID string, ownerAndRepo string) bool { return false } - config := p.getConfiguration() - ctx := context.Background() - owner, repo := parseOwnerAndRepo(ownerAndRepo, config.EnterpriseBaseURL) + owner, repo := parseOwnerAndRepo(ownerAndRepo, p.getBaseURL()) if owner == "" { return false @@ -153,7 +169,7 @@ func (p *Plugin) permissionToRepo(userID string, ownerAndRepo string) bool { } githubClient := p.githubConnect(*info.Token) - if result, _, err := githubClient.Repositories.Get(ctx, owner, repo); result == nil || err != nil { + if result, _, err := githubClient.Repositories.Get(context.Background(), owner, repo); result == nil || err != nil { if err != nil { mlog.Error(err.Error()) } @@ -705,16 +721,16 @@ func (p *Plugin) handleCommentMentionNotification(event *github.IssueCommentEven continue } - userId := p.getGitHubToUserIDMapping(username) - if userId == "" { + userID := p.getGitHubToUserIDMapping(username) + if userID == "" { continue } - if event.GetRepo().GetPrivate() && !p.permissionToRepo(userId, event.GetRepo().GetFullName()) { + if event.GetRepo().GetPrivate() && !p.permissionToRepo(userID, event.GetRepo().GetFullName()) { continue } - channel, err := p.API.GetDirectChannel(userId, p.BotUserID) + channel, err := p.API.GetDirectChannel(userID, p.BotUserID) if err != nil { continue } diff --git a/webapp/src/components/link_tooltip/link_tooltip.jsx b/webapp/src/components/link_tooltip/link_tooltip.jsx index 5abf675e5..cdf176f8f 100644 --- a/webapp/src/components/link_tooltip/link_tooltip.jsx +++ b/webapp/src/components/link_tooltip/link_tooltip.jsx @@ -92,7 +92,9 @@ export const LinkTooltip = ({href, connected}) => { {data.repo} on {date} + >{data.repo} + {' on '} + {date}
@@ -104,7 +106,7 @@ export const LinkTooltip = ({href, connected}) => {
{data.title}
- #{data.number} + {'#' + data.number}
{ style={{maxWidth: '140px'}} >{data.base.ref} - + {'←'} { href={href} target='_blank' rel='noopener noreferrer' - >See more + >{'See more'}
{/* Labels */} diff --git a/webapp/src/components/link_tooltip/tooltip.css b/webapp/src/components/link_tooltip/tooltip.css index a588af745..f2a837938 100644 --- a/webapp/src/components/link_tooltip/tooltip.css +++ b/webapp/src/components/link_tooltip/tooltip.css @@ -29,6 +29,7 @@ .github-tooltip .header { color: var(--light-gray); font-size: 12px; + white-space: nowrap; } .github-tooltip .header a { diff --git a/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx b/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx index c4bbcb647..abe6db6d0 100644 --- a/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx +++ b/webapp/src/components/sidebar_buttons/sidebar_buttons.jsx @@ -97,7 +97,7 @@ export default class SidebarButtons extends React.PureComponent { Connect to your GitHub} + overlay={{'Connect to your GitHub'}} > Your open pull requests} + overlay={{'Your open pull requests'}} > Pull requests needing review} + overlay={{'Pull requests needing review'}} > this.openRHS(RHSStates.REVIEWS)} @@ -163,7 +163,7 @@ export default class SidebarButtons extends React.PureComponent { Your assignments} + overlay={{'Your assignments'}} > this.openRHS(RHSStates.ASSIGNMENTS)} @@ -176,7 +176,7 @@ export default class SidebarButtons extends React.PureComponent { Unread messages} + overlay={{'Unread messages'}} > this.openRHS(RHSStates.UNREADS)} @@ -189,7 +189,7 @@ export default class SidebarButtons extends React.PureComponent { Refresh} + overlay={{'Refresh'}} > - #{item.number} + {' #' + item.number} ); } @@ -58,7 +58,7 @@ function GithubItems(props) { target='_blank' rel='noopener noreferrer' > - #{item.number} + {' #' + item.number} ); } @@ -114,7 +114,7 @@ function GithubItems(props) {
- {number} ({repoName}) + {number} {'(' + repoName + ')'}
- {(item.created_at || userName || milestone) && (
)} - {notificationReasons[item.reason]} - ) : null } + {item.reason ? ( + {(item.created_at || userName || milestone) && (
)} + {notificationReasons[item.reason]} +
) : null }
{reviews}
@@ -243,7 +242,7 @@ function getReviewText(item, style, secondLine) { } else { reviewName = 'reviews'; } - reviews = ({approved} out of {totalReviewers} {reviewName} complete.); + reviews = ({approved + ' out of ' + totalReviewers + ' ' + reviewName + ' complete.'}); } if (changesRequested > 0) { @@ -251,7 +250,7 @@ function getReviewText(item, style, secondLine) { Changes Requested} + overlay={{'Changes Requested'}} >
diff --git a/webapp/src/manifest.js b/webapp/src/manifest.js index 50d17b64f..3aace0030 100644 --- a/webapp/src/manifest.js +++ b/webapp/src/manifest.js @@ -1,2 +1,4 @@ +// This file is automatically generated. Do not modify it manually. + export const id = 'github'; export const version = '0.14.0';