Merge remote-tracking branch 'origin/master' into jjcollinge/pubsub-context

This commit is contained in:
Joni Collinge 2020-11-02 09:05:16 +00:00
commit 2e6b84ae40
153 changed files with 4149 additions and 1103 deletions

15
.codecov.yaml Normal file
View File

@ -0,0 +1,15 @@
coverage:
# Commit status https://docs.codecov.io/docs/commit-status are used
# to block PR based on coverage threshold.
status:
project:
default:
informational: true
patch:
# Disable the coverage threshold of the patch, so that PRs are
# only failing because of overall project coverage threshold.
# See https://docs.codecov.io/docs/commit-status#disabling-a-status.
default: false
comment:
# Delete old comment and post new one for new coverage information.
behavior: new

View File

@ -19,3 +19,13 @@ assignees: ''
## Steps to Reproduce the Problem
<!-- How can a maintainer reproduce this issue (be detailed) -->
## Release Note
<!-- How should the fix for this issue be communicated in our release notes? It can be populated later. -->
<!-- Keep it as a single line. Examples: -->
<!-- RELEASE NOTE: **ADD** New feature in Dapr. -->
<!-- RELEASE NOTE: **FIX** Bug in runtime. -->
<!-- RELEASE NOTE: **UPDATE** Runtime dependency. -->
RELEASE NOTE:

View File

@ -7,3 +7,13 @@ assignees: ''
---
## Describe the feature
## Release Note
<!-- How should this new feature be announced in our release notes? It can be populated later. -->
<!-- Keep it as a single line. Examples: -->
<!-- RELEASE NOTE: **ADD** New feature in Dapr. -->
<!-- RELEASE NOTE: **FIX** Bug in runtime. -->
<!-- RELEASE NOTE: **UPDATE** Runtime dependency. -->
RELEASE NOTE:

View File

@ -25,7 +25,7 @@ jobs:
GOOS: ${{ matrix.target_os }}
GOARCH: ${{ matrix.target_arch }}
GOPROXY: https://proxy.golang.org
GOLANGCI_LINT_VER: v1.26.0
GOLANGCI_LINT_VER: v1.31
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
@ -49,16 +49,19 @@ jobs:
go-version: ${{ env.GOVER }}
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VER }}
if: matrix.target_arch != 'arm'
run: |
curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b "${{ env.GOROOT }}/bin" "${{ env.GOLANGCI_LINT_VER }}"
- name: Run make lint
if: matrix.target_arch != 'arm' && matrix.target_os != 'windows'
run: make lint
- name: Run golangci-lint
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux'
uses: golangci/golangci-lint-action@v2.2.1
with:
version: ${{ env.GOLANGCI_LINT_VER }}
- name: Run make go.mod check-diff
if: matrix.target_arch != 'arm'
run: make go.mod check-diff
- name: Run make test
env:
COVERAGE_OPTS: "-coverprofile=coverage.txt -covermode=atomic"
if: matrix.target_arch != 'arm'
run: make test
- name: Codecov
if: matrix.target_arch == 'amd64' && matrix.target_os == 'linux'
uses: codecov/codecov-action@v1

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/dist
.idea
.vscode
.vscode
/vendor

2
CODEOWNERS Normal file
View File

@ -0,0 +1,2 @@
# These owners are the maintainers and approvers of this repo
* @maintainers-components-contrib @approvers-components-contrib

View File

@ -46,7 +46,7 @@ Before you file an issue, make sure you've checked the following:
- 👎 down-vote
1. For bugs
- Check it's not an environment issue. For example, if running on Kubernetes, make sure prerequisites are in place. (state stores, bindings, etc.)
- You have as much data as possible. This usually comes in the form of logs and/or stacktrace. If running on Kubernetes or other environment, look at the logs of the Dapr services (runtime, operator, placement service). More details on how to get logs can be found [here](https://github.com/dapr/docs/tree/master/best-practices/troubleshooting/logs.md).
- You have as much data as possible. This usually comes in the form of logs and/or stacktrace. If running on Kubernetes or other environment, look at the logs of the Dapr services (runtime, operator, placement service). More details on how to get logs can be found [here](https://docs.dapr.io/operations/troubleshooting/logs-troubleshooting/).
1. For proposals
- Many changes to the Dapr runtime may require changes to the API. In that case, the best place to discuss the potential feature is the main [Dapr repo](https://github.com/dapr/dapr).
- Other examples could include bindings, state stores or entirely new components.

View File

@ -51,7 +51,7 @@ endif
################################################################################
.PHONY: test
test:
go test ./...
go test ./... $(COVERAGE_OPTS)
################################################################################
# Target: lint #

7
OWNERS
View File

@ -1,7 +0,0 @@
maintainers:
- yaron2
- youngbupark
- Haishi2016
- lukekim
- amanbha
- msfussell

View File

@ -19,8 +19,12 @@ Available component types:
* [Secret Stores](secretstores/Readme.md)
* [Tracing Exporters](exporters/Readme.md)
For documentation on how components are being used in Dapr in a language/platform agnostic way, visit [Dapr Docs](https://github.com/dapr/docs).
For documentation on how components are being used in Dapr in a language/platform agnostic way, visit [Dapr Docs](https://docs.dapr.io).
## Contribution
* [Developing new component](docs/developing-component.md)
## Code of Conduct
Please refer to our [Dapr Community Code of Conduct](https://github.com/dapr/community/blob/master/CODE-OF-CONDUCT.md)

View File

@ -24,5 +24,6 @@ func GetClient(accessKey string, secretKey string, region string, endpoint strin
if err != nil {
return nil, err
}
return awsSession, nil
}

View File

@ -9,11 +9,10 @@ import (
"flag"
"path/filepath"
"k8s.io/client-go/rest"
"k8s.io/client-go/util/homedir"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/util/homedir"
)
// nolint:gochecknoglobals
@ -42,5 +41,6 @@ func GetKubeClient() (*kubernetes.Clientset, error) {
if err != nil {
return nil, err
}
return clientset, nil
}

View File

@ -3,11 +3,11 @@
Bindings provide a common way to trigger an application with events from external systems, or invoke an external system with optional data payloads.
Bindings are great for event-driven, on-demand compute and help reduce boilerplate code.
To get started with bindings visit the [How To Guide](https://github.com/dapr/docs/tree/master/howto#resources-bindings).
To get started with bindings visit the [How To Guide](https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-bindings/).
To view all the currently supported bindings visit: [Dapr bindings](https://github.com/dapr/docs/tree/master/concepts/bindings#supported-bindings-and-specs).
To view all the currently supported bindings visit: [Dapr bindings](https://docs.dapr.io/operations/components/setup-bindings/supported-bindings/).
For detailed binding specs visit [Dapr binding specs](https://github.com/dapr/docs/tree/master/reference/specs/bindings).
For detailed binding specs visit [Dapr binding specs](https://docs.dapr.io/operations/components/setup-bindings/supported-bindings/).
## Implementing a new binding
@ -41,4 +41,4 @@ For example, if running a component that takes in a SQL query and returns a resu
While components are not restricted to a list of supported operations, it's best to use common ones if the operation kind falls under that operation definition.
The list of common operations can be found [here](./requests.go).
After implementing a binding, the specification docs need to be updated via a PR: [Dapr docs](https://github.com/dapr/docs/tree/master/reference/specs/bindings).
After implementing a binding, the specification docs need to be updated via a PR: [Dapr docs](https://docs.dapr.io/operations/components/setup-bindings/supported-bindings/).

View File

@ -9,11 +9,10 @@ import (
"bytes"
"encoding/json"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/google/uuid"
"github.com/aliyun/aliyun-oss-go-sdk/oss"
)
// AliCloudOSS is a binding for an AliCloud OSS storage bucket
@ -47,6 +46,7 @@ func (s *AliCloudOSS) Init(metadata bindings.Metadata) error {
}
s.metadata = m
s.client = client
return nil
}
@ -63,9 +63,7 @@ func (s *AliCloudOSS) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRespo
s.logger.Debugf("key not found. generating key %s", key)
}
//r := bytes.NewReader(req.Data)
bucket, err := s.client.Bucket(s.metadata.Bucket)
if err != nil {
return nil, err
}
@ -90,12 +88,12 @@ func (s *AliCloudOSS) parseMetadata(metadata bindings.Metadata) (*ossMetadata, e
if err != nil {
return nil, err
}
return &m, nil
}
func (s *AliCloudOSS) getClient(metadata *ossMetadata) (*oss.Client, error) {
client, err := oss.New(metadata.Endpoint, metadata.AccessKeyID, metadata.AccessKey)
if err != nil {
return nil, err
}

245
bindings/apns/apns.go Normal file
View File

@ -0,0 +1,245 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package apns
import (
"bytes"
"context"
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"net/http"
"sync"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
jsoniter "github.com/json-iterator/go"
)
const (
collapseIDKey = "apns-collapse-id"
developmentKey = "development"
developmentPrefix = "https://api.sandbox.push.apple.com/3/device/"
deviceTokenKey = "device-token"
expirationKey = "apns-expiration"
keyIDKey = "key-id"
messageIDKey = "apns-id"
priorityKey = "apns-priority"
privateKeyKey = "private-key"
productionPrefix = "https://api.push.apple.com/3/device/"
pushTypeKey = "apns-push-type"
teamIDKey = "team-id"
topicKey = "apns-topic"
)
type notificationResponse struct {
MessageID string `json:"messageID"`
}
type errorResponse struct {
Reason string `json:"reason"`
Timestamp int64 `json:"timestamp"`
}
// APNS implements an outbound binding that allows services to send push
// notifications to Apple devices using Apple's Push Notification Service.
type APNS struct {
logger logger.Logger
client *http.Client
urlPrefix string
authorizationBuilder *authorizationBuilder
}
// NewAPNS will create a new APNS output binding.
func NewAPNS(logger logger.Logger) *APNS {
return &APNS{
logger: logger,
client: &http.Client{},
authorizationBuilder: &authorizationBuilder{
logger: logger,
mutex: sync.RWMutex{},
},
}
}
// Init will configure the APNS output binding using the metadata specified
// in the binding's configuration.
func (a *APNS) Init(metadata bindings.Metadata) error {
if err := a.makeURLPrefix(metadata); err != nil {
return err
}
if err := a.extractKeyID(metadata); err != nil {
return err
}
if err := a.extractTeamID(metadata); err != nil {
return err
}
return a.extractPrivateKey(metadata)
}
// Operations will return the set of operations supported by the APNS output
// binding. The APNS output binding only supports the "create" operation for
// sending new push notifications to the APNS service.
func (a *APNS) Operations() []bindings.OperationKind {
return []bindings.OperationKind{bindings.CreateOperation}
}
// Invoke is called by Dapr to send a push notification to the APNS output
// binding.
func (a *APNS) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
if req.Operation != bindings.CreateOperation {
return nil, fmt.Errorf("operation not supported: %v", req.Operation)
}
return a.sendPushNotification(req)
}
func (a *APNS) sendPushNotification(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
deviceToken, ok := req.Metadata[deviceTokenKey]
if !ok || deviceToken == "" {
return nil, errors.New("the device-token parameter is required")
}
httpResponse, err := a.sendPushNotificationToAPNS(deviceToken, req)
if err != nil {
return nil, err
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode == http.StatusOK {
return makeSuccessResponse(httpResponse)
}
return makeErrorResponse(httpResponse)
}
func (a *APNS) sendPushNotificationToAPNS(deviceToken string, req *bindings.InvokeRequest) (*http.Response, error) {
url := a.urlPrefix + deviceToken
httpRequest, err := http.NewRequestWithContext(
context.Background(),
http.MethodPost,
url,
bytes.NewReader(req.Data),
)
if err != nil {
return nil, err
}
authorizationHeader, err := a.authorizationBuilder.getAuthorizationHeader()
if err != nil {
return nil, err
}
httpRequest.Header.Add("authorization", authorizationHeader)
addRequestHeader(pushTypeKey, req.Metadata, httpRequest)
addRequestHeader(messageIDKey, req.Metadata, httpRequest)
addRequestHeader(expirationKey, req.Metadata, httpRequest)
addRequestHeader(priorityKey, req.Metadata, httpRequest)
addRequestHeader(topicKey, req.Metadata, httpRequest)
addRequestHeader(collapseIDKey, req.Metadata, httpRequest)
return a.client.Do(httpRequest)
}
func (a *APNS) makeURLPrefix(metadata bindings.Metadata) error {
if value, ok := metadata.Properties[developmentKey]; ok && value != "" {
switch value {
case "true":
a.logger.Debug("Using the development APNS service")
a.urlPrefix = developmentPrefix
case "false":
a.logger.Debug("Using the production APNS service")
a.urlPrefix = productionPrefix
default:
return fmt.Errorf(
"invalid value for development parameter: %v",
value,
)
}
} else {
a.logger.Debug("Using the production APNS service")
a.urlPrefix = productionPrefix
}
return nil
}
func (a *APNS) extractKeyID(metadata bindings.Metadata) error {
if value, ok := metadata.Properties[keyIDKey]; ok && value != "" {
a.authorizationBuilder.keyID = value
return nil
}
return errors.New("the key-id parameter is required")
}
func (a *APNS) extractTeamID(metadata bindings.Metadata) error {
if value, ok := metadata.Properties[teamIDKey]; ok && value != "" {
a.authorizationBuilder.teamID = value
return nil
}
return errors.New("the team-id parameter is required")
}
func (a *APNS) extractPrivateKey(metadata bindings.Metadata) error {
if value, ok := metadata.Properties[privateKeyKey]; ok && value != "" {
block, _ := pem.Decode([]byte(value))
if block == nil {
return errors.New("unable to read the private key")
}
privateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes)
if err != nil {
return err
}
a.authorizationBuilder.privateKey = privateKey
} else {
return errors.New("the private-key parameter is required")
}
return nil
}
func addRequestHeader(key string, metadata map[string]string, httpRequest *http.Request) {
if value, ok := metadata[key]; ok && value != "" {
httpRequest.Header.Add(key, value)
}
}
func makeSuccessResponse(httpResponse *http.Response) (*bindings.InvokeResponse, error) {
messageID := httpResponse.Header.Get(messageIDKey)
output := notificationResponse{MessageID: messageID}
var data bytes.Buffer
encoder := jsoniter.NewEncoder(&data)
err := encoder.Encode(output)
if err != nil {
return nil, err
}
return &bindings.InvokeResponse{Data: data.Bytes()}, nil
}
func makeErrorResponse(httpResponse *http.Response) (*bindings.InvokeResponse, error) {
var errorReply errorResponse
decoder := jsoniter.NewDecoder(httpResponse.Body)
err := decoder.Decode(&errorReply)
if err == nil {
err = errors.New(errorReply.Reason)
}
return nil, err
}

353
bindings/apns/apns_test.go Normal file
View File

@ -0,0 +1,353 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package apns
import (
"bytes"
"io/ioutil"
"net/http"
"strings"
"testing"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
jsoniter "github.com/json-iterator/go"
"github.com/stretchr/testify/assert"
)
const (
testKeyID = "012345678"
testTeamID = "876543210"
// This is a valid PKCS #8 payload, but the key was generated for testing
// use and is not being used in any production service.
testPrivateKey = "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgHZdKErL0xQ3yalg+\nbUMpTpfo4bRVxYMnowSMkBIS3OSgCgYIKoZIzj0DAQehRANCAARjr0Ft+hWAeAfY\nkkOBk8GzMlV4Mo/APwcuXRlAHqkSUKi453YqgAPygkCNBmOhNWgynUp+XGxuj6in\nofsBN1Rw\n-----END PRIVATE KEY-----"
)
func TestInit(t *testing.T) {
testLogger := logger.NewLogger("test")
t.Run("uses the development service", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
developmentKey: "true",
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Nil(t, err)
assert.Equal(t, developmentPrefix, binding.urlPrefix)
})
t.Run("uses the production service", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
developmentKey: "false",
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Nil(t, err)
assert.Equal(t, productionPrefix, binding.urlPrefix)
})
t.Run("defaults to the production service", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Nil(t, err)
assert.Equal(t, productionPrefix, binding.urlPrefix)
})
t.Run("invalid development value", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
developmentKey: "True",
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Error(t, err, "invalid value for development parameter: True")
})
t.Run("the key ID is required", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Error(t, err, "the key-id parameter is required")
})
t.Run("valid key ID", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Nil(t, err)
assert.Equal(t, testKeyID, binding.authorizationBuilder.keyID)
})
t.Run("the team ID is required", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
keyIDKey: testKeyID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Error(t, err, "the team-id parameter is required")
})
t.Run("valid team ID", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Nil(t, err)
assert.Equal(t, testTeamID, binding.authorizationBuilder.teamID)
})
t.Run("the private key is required", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
keyIDKey: testKeyID,
teamIDKey: testTeamID,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Error(t, err, "the private-key parameter is required")
})
t.Run("valid private key", func(t *testing.T) {
metadata := bindings.Metadata{
Properties: map[string]string{
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
binding := NewAPNS(testLogger)
err := binding.Init(metadata)
assert.Nil(t, err)
assert.NotNil(t, binding.authorizationBuilder.privateKey)
})
}
func TestOperations(t *testing.T) {
testLogger := logger.NewLogger("test")
testBinding := NewAPNS(testLogger)
operations := testBinding.Operations()
assert.Equal(t, 1, len(operations))
assert.Equal(t, bindings.CreateOperation, operations[0])
}
func TestInvoke(t *testing.T) {
testLogger := logger.NewLogger("test")
successRequest := &bindings.InvokeRequest{
Operation: bindings.CreateOperation,
Metadata: map[string]string{
deviceTokenKey: "1234567890",
pushTypeKey: "alert",
messageIDKey: "123",
expirationKey: "1234567890",
priorityKey: "10",
topicKey: "test",
collapseIDKey: "1234567",
},
}
t.Run("operation must be create", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
req := &bindings.InvokeRequest{Operation: bindings.DeleteOperation}
_, err := testBinding.Invoke(req)
assert.Error(t, err, "operation not supported: delete")
})
t.Run("the device token is required", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
req := &bindings.InvokeRequest{
Operation: bindings.CreateOperation,
Metadata: map[string]string{},
}
_, err := testBinding.Invoke(req)
assert.Error(t, err, "the device-token parameter is required")
})
t.Run("the authorization header is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Authorization")
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the push type header is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Apns-Push-Type")
assert.Equal(t, "alert", req.Header.Get(pushTypeKey))
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the message ID is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Apns-Id")
assert.Equal(t, "123", req.Header.Get(messageIDKey))
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the expiration is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Apns-Expiration")
assert.Equal(t, "1234567890", req.Header.Get(expirationKey))
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the priority is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Apns-Priority")
assert.Equal(t, "10", req.Header.Get(priorityKey))
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the topic is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Apns-Topic")
assert.Equal(t, "test", req.Header.Get(topicKey))
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the collapse ID is sent", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
assert.Contains(t, req.Header, "Apns-Collapse-Id")
assert.Equal(t, "1234567", req.Header.Get(collapseIDKey))
return successResponse()
})
_, _ = testBinding.Invoke(successRequest)
})
t.Run("the message ID is returned", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
return successResponse()
})
response, err := testBinding.Invoke(successRequest)
assert.Nil(t, err)
assert.NotNil(t, response.Data)
var body notificationResponse
decoder := jsoniter.NewDecoder(bytes.NewReader(response.Data))
err = decoder.Decode(&body)
assert.Nil(t, err)
assert.Equal(t, "12345", body.MessageID)
})
t.Run("returns the error code", func(t *testing.T) {
testBinding := makeTestBinding(t, testLogger)
testBinding.client = newTestClient(func(req *http.Request) *http.Response {
body := "{\"reason\":\"BadDeviceToken\"}"
return &http.Response{
StatusCode: http.StatusBadRequest,
Body: ioutil.NopCloser(strings.NewReader(body)),
}
})
_, err := testBinding.Invoke(successRequest)
assert.Error(t, err, "BadDeviceToken")
})
}
func makeTestBinding(t *testing.T, log logger.Logger) *APNS {
testBinding := NewAPNS(log)
bindingMetadata := bindings.Metadata{
Properties: map[string]string{
developmentKey: "true",
keyIDKey: testKeyID,
teamIDKey: testTeamID,
privateKeyKey: testPrivateKey,
},
}
err := testBinding.Init(bindingMetadata)
assert.Nil(t, err)
return testBinding
}
func successResponse() *http.Response {
response := &http.Response{
StatusCode: http.StatusOK,
Header: http.Header{},
}
response.Header.Add(messageIDKey, "12345")
return response
}
// http://hassansin.github.io/Unit-Testing-http-client-in-Go
type roundTripFunc func(req *http.Request) *http.Response
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
return f(req), nil
}
func newTestClient(fn roundTripFunc) *http.Client {
return &http.Client{Transport: fn}
}

View File

@ -0,0 +1,74 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package apns
import (
"sync"
"time"
"github.com/dapr/dapr/pkg/logger"
"github.com/dgrijalva/jwt-go"
)
// The "issued at" timestamp in the JWT must be within one hour from the
// APNS server time. I set the expiration time at 55 minutes to ensure that
// a new certificate gets generated before it gets too close and risking a
// failure.
const expirationMinutes = time.Minute * 55
type authorizationBuilder struct {
logger logger.Logger
mutex sync.RWMutex
authorizationHeader string
tokenExpiresAt time.Time
keyID string
teamID string
privateKey interface{}
}
func (a *authorizationBuilder) getAuthorizationHeader() (string, error) {
authorizationHeader, ok := a.readAuthorizationHeader()
if ok {
return authorizationHeader, nil
}
return a.generateAuthorizationHeader()
}
func (a *authorizationBuilder) readAuthorizationHeader() (string, bool) {
a.mutex.RLock()
defer a.mutex.RUnlock()
if time.Now().After(a.tokenExpiresAt) {
return "", false
}
return a.authorizationHeader, true
}
func (a *authorizationBuilder) generateAuthorizationHeader() (string, error) {
a.mutex.Lock()
defer a.mutex.Unlock()
a.logger.Debug("Authorization token expired; generating new token")
now := time.Now()
claims := jwt.StandardClaims{
IssuedAt: now.Unix(),
Issuer: a.teamID,
}
token := jwt.NewWithClaims(jwt.SigningMethodES256, claims)
token.Header["kid"] = a.keyID
signedToken, err := token.SignedString(a.privateKey)
if err != nil {
return "", err
}
a.authorizationHeader = "bearer " + signedToken
a.tokenExpiresAt = now.Add(expirationMinutes)
return a.authorizationHeader, nil
}

161
bindings/apns/doc.go Normal file
View File

@ -0,0 +1,161 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
// Package apns implements an output binding for Dapr that allows services to
// send push notifications to Apple devices and Mac computers using Apple's
// Push Notification Service (APNS).
//
// Configuring the Binding
//
// To use the APNS output binding, you will need to create the binding
// configuration and add it to your components directory. The binding
// configuration will contain parameters that will allow the binding to
// connect to the APNS service specified as metadata.
//
// The APNS binding will need a cryptographic private key in order to generate
// authentication tokens for the APNS service. The private key can be generated
// from the Apple Developer Portal and is provided as a PKCS #8 file with the
// private key stored in PEM format. The private key should be stored in the
// Dapr secret store and not stored directly in the binding's configuration
// file.
//
// A sample configuration file for the APNS binding is shown below:
//
// apiVersion: dapr.io/v1alpha1
// kind: Component
// metadata:
// name: apns
// namespace: default
// spec:
// type: bindings.apns
// metadata:
// - name: development
// value: false
// - name: key-id
// value: PUT-KEY-ID-HERE
// - name: team-id
// value: PUT-APPLE-TEAM-ID-HERE
// - name: private-key
// secretKeyRef:
// name: apns-secrets
// key: private-key
//
// If using Kubernetes, a sample secret configuration may look like this:
//
// apiVersion: v1
// kind: Secret
// metadata:
// name: apns-secrets
// namespace: default
// stringData:
// private-key: |
// -----BEGIN PRIVATE KEY-----
// KEY-DATA-GOES-HERE
// -----END PRIVATE KEY-----
//
// The development parameter can be either "true" or "false". The development
// parameter controls which APNS service is used. If development is set to
// true, then the sandbox APNS service will be used to send push notifications
// to devices. If development is set to false, the production APNS service will
// be used to send push notifications. If not specified, the production service
// will be chosen by default.
//
// Push Notification Format
//
// The APNS binding is a pass-through wrapper over the Apple Push Notification
// Service. The APNS binding will send the request directly to the APNS service
// without any translation. It is therefore important to understand the payload
// for push notifications expected by the APNS service. The payload format is
// documented at https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification.
//
// Requests sent to the APNS binding should be a JSON object. A simple push
// notification appears below:
//
// {
// "aps": {
// "alert": {
// "title": "New Updates!",
// "body": "New updates are now available for your review."
// }
// }
// }
//
// The aps child object contains the push notification details that are used
// by the Apple Push Notification Service and target devices to route and show
// the push notification. Additional objects or values can be added to the push
// notification envelope for use by applications to handle the push
// notification.
//
// The APNS binding accepts several metadata values that are mapped directly
// to HTTP headers in the APNS publish request. Below is a summary of the valid
// metadata fields. For more information, please see
// https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/generating_a_remote_notification.
//
// * apns-push-type: Identifies the content of the notification payload. One of
// alert, background, voip, complication, fileprovider, mdm.
//
// * apns-id: a UUID that uniquely identifies the push notification. This value
// is returned by APNS if provided and can be used to track notifications.
//
// * apns-expiration: The date/time at which the notification is no longer
// valid and should not be delivered. This value is the number of seconds
// since the UNIX epoch (January 1, 1970 at 00:00 UTC). If not specified or
// if 0, the message is sent once immediately and then discarded.
//
// * apns-priority: If 10, the notification is sent immediately. If 5, the
// notification is sent based on power conditions of the user's device.
// Defaults to 10.
//
// * apns-topic: The topic for the notification. Typically this is the bundle
// identifier of the target app.
//
// * apns-collapse-id: A correlation identifier that will cause notifications
// to be displayed as a group on the target device. For example, multiple
// notifications from a chat room may have the same identifier causing them
// to show up together in the device's notifications list.
//
// Sending a Push Notification Using the APNS Binding
//
// A simple request to the APNS binding looks like this:
//
// {
// "data": {
// "aps": {
// "alert": {
// "title": "New Updates!",
// "body": "New updates are available for your review."
// }
// }
// },
// "metadata": {
// "device-token": "PUT-DEVICE-TOKEN-HERE",
// "apns-push-type": "alert",
// "apns-priority": "10",
// "apns-topic": "com.example.helloworld"
// },
// "operation": "create"
// }
//
// The device-token metadata field is required and should contain the token
// for the device that will receive the push notification. Only one device
// can be specified per request to the APNS binding.
//
// The APNS binding only supports one operation: create. Specifying any other
// operation name will result in a runtime error.
//
// If the push notification is successfully sent, the response will be a JSON
// object containing the message ID. If a message ID was not specified using
// the apns-id metadata value, then the Apple Push Notification Serivce will
// generate a unique ID and will return it.
//
// {
// "messageID": "12345678-1234-1234-1234-1234567890AB"
// }
//
// If the push notification could not be sent due to an authentication error
// or payload error, the error code returned by Apple will be returned. For
// a list of error codes and their meanings, see
// https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/handling_notification_responses_from_apns.
package apns

View File

@ -8,17 +8,15 @@ package dynamodb
import (
"encoding/json"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/dapr/pkg/logger"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/dynamodb"
"github.com/aws/aws-sdk-go/service/dynamodb/dynamodbattribute"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
)
//DynamoDB allows performing stateful operations on AWS DynamoDB
// DynamoDB allows performing stateful operations on AWS DynamoDB
type DynamoDB struct {
client *dynamodb.DynamoDB
table string
@ -52,6 +50,7 @@ func (d *DynamoDB) Init(metadata bindings.Metadata) error {
d.client = client
d.table = meta.Table
return nil
}
@ -95,6 +94,7 @@ func (d *DynamoDB) getDynamoDBMetadata(spec bindings.Metadata) (*dynamoDBMetadat
if err != nil {
return nil, err
}
return &meta, nil
}
@ -105,5 +105,6 @@ func (d *DynamoDB) getClient(metadata *dynamoDBMetadata) (*dynamodb.DynamoDB, er
}
c := dynamodb.New(sess)
return c, nil
}

View File

@ -15,12 +15,11 @@ import (
"syscall"
"time"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/request"
"github.com/aws/aws-sdk-go/service/kinesis"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/google/uuid"
@ -55,10 +54,10 @@ type kinesisMetadata struct {
type kinesisConsumerMode string
const (
//ExtendedFanout - dedicated throughput through data stream api
// ExtendedFanout - dedicated throughput through data stream api
ExtendedFanout kinesisConsumerMode = "extended"
//SharedThroughput - shared throughput using checkpoint and monitoring
// SharedThroughput - shared throughput using checkpoint and monitoring
SharedThroughput kinesisConsumerMode = "shared"
partitionKeyName = "partitionKey"
@ -104,7 +103,6 @@ func (a *AWSKinesis) Init(metadata bindings.Metadata) error {
stream, err := client.DescribeStream(&kinesis.DescribeStreamInput{
StreamName: streamName,
})
if err != nil {
return err
}
@ -119,6 +117,7 @@ func (a *AWSKinesis) Init(metadata bindings.Metadata) error {
a.streamARN = stream.StreamDescription.StreamARN
a.metadata = m
a.client = client
return nil
}
@ -136,6 +135,7 @@ func (a *AWSKinesis) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRespon
Data: req.Data,
PartitionKey: &partitionKey,
})
return nil, err
}
@ -173,6 +173,7 @@ func (a *AWSKinesis) Subscribe(ctx context.Context, streamDesc kinesis.StreamDes
consumerARN, err := a.ensureConsumer(streamDesc.StreamARN)
if err != nil {
a.logger.Error(err)
return err
}
@ -189,9 +190,9 @@ func (a *AWSKinesis) Subscribe(ctx context.Context, streamDesc kinesis.StreamDes
ShardId: s.ShardId,
StartingPosition: &kinesis.StartingPosition{Type: aws.String(kinesis.ShardIteratorTypeLatest)},
})
if err != nil {
a.logger.Error(err)
return err
}
@ -219,9 +220,9 @@ func (a *AWSKinesis) ensureConsumer(streamARN *string) (*string, error) {
ConsumerName: &a.metadata.ConsumerName,
StreamARN: streamARN,
})
if err != nil {
arn, err := a.registerConsumer(streamARN)
return arn, err
}
@ -233,7 +234,6 @@ func (a *AWSKinesis) registerConsumer(streamARN *string) (*string, error) {
ConsumerName: &a.metadata.ConsumerName,
StreamARN: streamARN,
})
if err != nil {
return nil, err
}
@ -257,6 +257,7 @@ func (a *AWSKinesis) deregisterConsumer(streamARN *string, consumerARN *string)
StreamARN: streamARN,
ConsumerName: &a.metadata.ConsumerName,
})
return err
}
@ -284,6 +285,7 @@ func (a *AWSKinesis) waitUntilConsumerExists(ctx aws.Context, input *kinesis.Des
req, _ := a.client.DescribeStreamConsumerRequest(inCpy)
req.SetContext(ctx)
req.ApplyOptions(opts...)
return req, nil
},
}
@ -299,6 +301,7 @@ func (a *AWSKinesis) getClient(metadata *kinesisMetadata) (*kinesis.Kinesis, err
}
k := kinesis.New(sess)
return k, nil
}
@ -313,6 +316,7 @@ func (a *AWSKinesis) parseMetadata(metadata bindings.Metadata) (*kinesisMetadata
if err != nil {
return nil, err
}
return &m, nil
}

View File

@ -9,14 +9,12 @@ import (
"bytes"
"encoding/json"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
"github.com/google/uuid"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/s3/s3manager"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/google/uuid"
)
// AWSS3 is a binding for an AWS S3 storage bucket
@ -51,6 +49,7 @@ func (s *AWSS3) Init(metadata bindings.Metadata) error {
}
s.metadata = m
s.uploader = uploader
return nil
}
@ -73,6 +72,7 @@ func (s *AWSS3) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, e
Key: aws.String(key),
Body: r,
})
return nil, err
}
@ -87,6 +87,7 @@ func (s *AWSS3) parseMetadata(metadata bindings.Metadata) (*s3Metadata, error) {
if err != nil {
return nil, err
}
return &m, nil
}
@ -97,5 +98,6 @@ func (s *AWSS3) getClient(metadata *s3Metadata) (*s3manager.Uploader, error) {
}
uploader := s3manager.NewUploader(sess)
return uploader, nil
}

View File

@ -9,12 +9,10 @@ import (
"encoding/json"
"fmt"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/dapr/pkg/logger"
"github.com/aws/aws-sdk-go/service/sns"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
)
// AWSSNS is an AWS SNS binding
@ -55,6 +53,7 @@ func (a *AWSSNS) Init(metadata bindings.Metadata) error {
}
a.client = client
a.topicARN = m.TopicArn
return nil
}
@ -69,6 +68,7 @@ func (a *AWSSNS) parseMetadata(metadata bindings.Metadata) (*snsMetadata, error)
if err != nil {
return nil, err
}
return &m, nil
}
@ -78,6 +78,7 @@ func (a *AWSSNS) getClient(metadata *snsMetadata) (*sns.SNS, error) {
return nil, err
}
c := sns.New(sess)
return c, nil
}
@ -105,5 +106,6 @@ func (a *AWSSNS) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse,
if err != nil {
return nil, err
}
return nil, nil
}

View File

@ -59,6 +59,7 @@ func (a *AWSSQS) Init(metadata bindings.Metadata) error {
a.QueueURL = resultURL.QueueUrl
a.Client = client
return nil
}
@ -72,6 +73,7 @@ func (a *AWSSQS) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse,
MessageBody: &msgBody,
QueueUrl: a.QueueURL,
})
return nil, err
}
@ -125,6 +127,7 @@ func (a *AWSSQS) parseSQSMetadata(metadata bindings.Metadata) (*sqsMetadata, err
if err != nil {
return nil, err
}
return &m, nil
}

View File

@ -6,6 +6,7 @@
package blobstorage
import (
"bytes"
"context"
b64 "encoding/base64"
"encoding/json"
@ -13,21 +14,21 @@ import (
"net/url"
"strconv"
"github.com/dapr/dapr/pkg/logger"
"github.com/google/uuid"
"github.com/Azure/azure-storage-blob-go/azblob"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/google/uuid"
)
const (
blobName = "blobName"
contentType = "ContentType"
contentMD5 = "ContentMD5"
contentEncoding = "ContentEncoding"
contentLanguage = "ContentLanguage"
contentDisposition = "ContentDisposition"
cacheControl = "CacheControl"
blobName = "blobName"
contentType = "ContentType"
contentMD5 = "ContentMD5"
contentEncoding = "ContentEncoding"
contentLanguage = "ContentLanguage"
contentDisposition = "ContentDisposition"
cacheControl = "CacheControl"
defaultGetBlobRetryCount = 10
)
// AzureBlobStorage allows saving blobs to an Azure Blob Storage account
@ -39,9 +40,15 @@ type AzureBlobStorage struct {
}
type blobStorageMetadata struct {
StorageAccount string `json:"storageAccount"`
StorageAccessKey string `json:"storageAccessKey"`
Container string `json:"container"`
StorageAccount string `json:"storageAccount"`
StorageAccessKey string `json:"storageAccessKey"`
Container string `json:"container"`
DecodeBase64 string `json:"decodeBase64"`
GetBlobRetryCount int `json:"getBlobRetryCount"`
}
type createResponse struct {
BlobURL string `json:"blobURL"`
}
// NewAzureBlobStorage returns a new Azure Blob Storage instance
@ -72,6 +79,7 @@ func (a *AzureBlobStorage) Init(metadata bindings.Metadata) error {
// Don't return error, container might already exist
a.logger.Debugf("error creating container: %s", err)
a.containerURL = containerURL
return nil
}
@ -87,23 +95,19 @@ func (a *AzureBlobStorage) parseMetadata(metadata bindings.Metadata) (*blobStora
if err != nil {
return nil, err
}
if m.GetBlobRetryCount == 0 {
m.GetBlobRetryCount = defaultGetBlobRetryCount
}
return &m, nil
}
func (a *AzureBlobStorage) Operations() []bindings.OperationKind {
return []bindings.OperationKind{bindings.CreateOperation}
return []bindings.OperationKind{bindings.CreateOperation, bindings.GetOperation}
}
func (a *AzureBlobStorage) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
name := ""
if val, ok := req.Metadata[blobName]; ok && val != "" {
name = val
delete(req.Metadata, blobName)
} else {
name = uuid.New().String()
}
blobURL := a.containerURL.NewBlockBlobURL(name)
func (a *AzureBlobStorage) create(blobURL azblob.BlockBlobURL, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
var blobHTTPHeaders azblob.BlobHTTPHeaders
if val, ok := req.Metadata[contentType]; ok && val != "" {
blobHTTPHeaders.ContentType = val
@ -135,12 +139,82 @@ func (a *AzureBlobStorage) Invoke(req *bindings.InvokeRequest) (*bindings.Invoke
}
// Unescape data which will still be a JSON string
unescapedData, _ := strconv.Unquote(string(req.Data))
unescapedData, unescapeError := strconv.Unquote(string(req.Data))
_, err := azblob.UploadBufferToBlockBlob(context.Background(), []byte(unescapedData), blobURL, azblob.UploadToBlockBlobOptions{
if unescapeError != nil {
return nil, unescapeError
}
data := []byte(unescapedData)
// The "true" is the only allowed positive value. Other positive variations like "True" not acceptable.
if a.metadata.DecodeBase64 == "true" {
decoded, decodeError := b64.StdEncoding.DecodeString(unescapedData)
if decodeError != nil {
return nil, decodeError
}
data = decoded
}
_, err := azblob.UploadBufferToBlockBlob(context.Background(), data, blobURL, azblob.UploadToBlockBlobOptions{
Parallelism: 16,
Metadata: req.Metadata,
BlobHTTPHeaders: blobHTTPHeaders,
})
return nil, err
if err != nil {
return nil, fmt.Errorf("error uploading az blob: %s", err)
}
resp := createResponse{
BlobURL: blobURL.String(),
}
b, err := json.Marshal(resp)
if err != nil {
return nil, fmt.Errorf("error marshalling create response for azure blob: %s", err)
}
return &bindings.InvokeResponse{
Data: b,
}, nil
}
func (a *AzureBlobStorage) get(blobURL azblob.BlockBlobURL, req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
resp, err := blobURL.Download(context.TODO(), 0, azblob.CountToEnd, azblob.BlobAccessConditions{}, false)
if err != nil {
return nil, fmt.Errorf("error downloading az blob: %s", err)
}
bodyStream := resp.Body(azblob.RetryReaderOptions{MaxRetryRequests: a.metadata.GetBlobRetryCount})
b := bytes.Buffer{}
_, err = b.ReadFrom(bodyStream)
if err != nil {
return nil, fmt.Errorf("error reading az blob body: %s", err)
}
return &bindings.InvokeResponse{
Data: b.Bytes(),
}, nil
}
func (a *AzureBlobStorage) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
name := ""
if val, ok := req.Metadata[blobName]; ok && val != "" {
name = val
delete(req.Metadata, blobName)
} else {
name = uuid.New().String()
}
blobURL := a.containerURL.NewBlockBlobURL(name)
switch req.Operation {
case bindings.CreateOperation:
return a.create(blobURL, req)
case bindings.GetOperation:
return a.get(blobURL, req)
case bindings.DeleteOperation, bindings.ListOperation:
fallthrough
default:
return nil, fmt.Errorf("unsupported operation %s", req.Operation)
}
}

View File

@ -15,11 +15,12 @@ import (
func TestParseMetadata(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{"storageAccount": "account", "storageAccessKey": "key", "container": "test"}
m.Properties = map[string]string{"storageAccount": "account", "storageAccessKey": "key", "container": "test", "decodeBase64": "true"}
blonStorage := NewAzureBlobStorage(logger.NewLogger("test"))
meta, err := blonStorage.parseMetadata(m)
assert.Nil(t, err)
assert.Equal(t, "test", meta.Container)
assert.Equal(t, "account", meta.StorageAccount)
assert.Equal(t, "key", meta.StorageAccessKey)
assert.Equal(t, "true", meta.DecodeBase64)
}

View File

@ -79,6 +79,7 @@ func (c *CosmosDB) Init(metadata bindings.Metadata) error {
c.collection = &colls[0]
c.client = client
return nil
}
@ -94,6 +95,7 @@ func (c *CosmosDB) parseMetadata(metadata bindings.Metadata) (*cosmosDBCredentia
if err != nil {
return nil, err
}
return &creds, nil
}

View File

@ -58,7 +58,7 @@ func TestPartitionKeyValue(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, "earth", val)
//Invalid nested partition key
// Invalid nested partition key
_, err = cosmosDB.getPartitionKeyValue("address.notexists", obj)
assert.NotNil(t, err)

View File

@ -11,7 +11,6 @@ import (
"errors"
"fmt"
"io/ioutil"
"time"
"github.com/Azure/azure-sdk-for-go/services/preview/eventgrid/mgmt/2020-04-01-preview/eventgrid"
@ -115,6 +114,7 @@ func (a *AzureEventGrid) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRe
err := a.ensureOutputBindingMetadata()
if err != nil {
a.logger.Error(err.Error())
return nil, err
}
@ -133,12 +133,14 @@ func (a *AzureEventGrid) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRe
err = client.Do(request, response)
if err != nil {
a.logger.Error(err.Error())
return nil, err
}
if response.StatusCode() != fasthttp.StatusOK {
body := response.Body()
a.logger.Error(string(body))
return nil, errors.New(string(body))
}
@ -174,10 +176,12 @@ func (a *AzureEventGrid) ensureInputBindingMetadata() error {
func (a *AzureEventGrid) ensureOutputBindingMetadata() error {
if a.metadata.AccessKey == "" {
msg := fmt.Sprintf("metadata field 'AccessKey' is empty in EventGrid binding (%s)", a.metadata.Name)
return errors.New(msg)
}
if a.metadata.TopicEndpoint == "" {
msg := fmt.Sprintf("metadata field 'TopicEndpoint' is empty in EventGrid binding (%s)", a.metadata.Name)
return errors.New(msg)
}
@ -205,6 +209,7 @@ func (a *AzureEventGrid) parseMetadata(metadata bindings.Metadata) (*azureEventG
if eventGridMetadata.EventSubscriptionName == "" {
eventGridMetadata.EventSubscriptionName = metadata.Name
}
return &eventGridMetadata, nil
}
@ -243,6 +248,7 @@ func (a *AzureEventGrid) createSubscription() error {
if err != nil {
return err
}
return errors.New(string(bodyBytes))
}

View File

@ -91,6 +91,7 @@ func (a *AzureEventHubs) Init(metadata bindings.Metadata) error {
}
a.hub = hub
return nil
}
@ -203,6 +204,7 @@ func (a *AzureEventHubs) RegisterPartitionedEventProcessor(handler func(*binding
Data: event.Data,
})
}
return nil
}
@ -233,6 +235,7 @@ func contains(arr []string, str string) bool {
return true
}
}
return false
}
@ -250,7 +253,6 @@ func (a *AzureEventHubs) RegisterEventProcessor(handler func(*bindings.ReadRespo
}
processor, err := eph.NewFromConnectionString(context.Background(), a.metadata.connectionString, leaserCheckpointer, leaserCheckpointer, eph.WithNoBanner(), eph.WithConsumerGroup(a.metadata.consumerGroup))
if err != nil {
return err
}

View File

@ -58,7 +58,8 @@ func TestParseMetadata(t *testing.T) {
"missing storageContainerName",
map[string]string{consumerGroup: "fake", connectionString: "fake", storageAccountName: "name", storageAccountKey: "key"},
missingStorageContainerNameErrorMsg,
}}
},
}
for _, c := range invalidConfigTestCases {
t.Run(c.name, func(t *testing.T) {

View File

@ -69,6 +69,7 @@ func (a *AzureServiceBusQueues) Init(metadata bindings.Metadata) error {
for _, q := range queues {
if q.Name == a.metadata.QueueName {
entity = q
break
}
}
@ -97,6 +98,7 @@ func (a *AzureServiceBusQueues) Init(metadata bindings.Metadata) error {
return err
}
a.client = client
return nil
}
@ -164,11 +166,13 @@ func (a *AzureServiceBusQueues) Read(handler func(*bindings.ReadResponse) error)
if err == nil {
return msg.Complete(ctx)
}
return msg.Abandon(ctx)
}
if err := a.client.Receive(context.Background(), sbHandler); err != nil {
return err
}
return nil
}

View File

@ -7,6 +7,7 @@ package signalr
import (
"bytes"
"context"
"fmt"
"io/ioutil"
"net/http"
@ -119,7 +120,7 @@ func (s *SignalR) resolveAPIURL(req *bindings.InvokeRequest) (string, error) {
}
func (s *SignalR) sendMessageToSignalR(url string, token string, data []byte) error {
httpReq, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", url, bytes.NewBuffer(data))
if err != nil {
return err
}

View File

@ -160,6 +160,7 @@ func (t *mockTransport) reset() {
func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
atomic.AddInt32(&t.requestCount, 1)
t.request = req
return t.response, t.errToReturn
}

View File

@ -62,6 +62,7 @@ func (d *AzureQueueHelper) Init(accountName string, accountKey string, queueName
if err != nil {
return err
}
return nil
}
@ -75,6 +76,7 @@ func (d *AzureQueueHelper) Write(data []byte, ttl *time.Duration) error {
ttl = &ttlToUse
}
_, err := messagesURL.Enqueue(ctx, s, time.Second*0, *ttl)
return err
}
@ -87,6 +89,7 @@ func (d *AzureQueueHelper) Read(ctx context.Context, consumer *consumer) error {
if res.NumMessages() == 0 {
// Queue was empty so back off by 10 seconds before trying again
time.Sleep(10 * time.Second)
return nil
}
mt := res.Message(0).Text
@ -116,6 +119,7 @@ func (d *AzureQueueHelper) Read(ctx context.Context, consumer *consumer) error {
if err != nil {
return err
}
return nil
}
@ -165,6 +169,7 @@ func (a *AzureStorageQueues) Init(metadata bindings.Metadata) error {
if err != nil {
return err
}
return nil
}
@ -210,6 +215,7 @@ func (a *AzureStorageQueues) Invoke(req *bindings.InvokeRequest) (*bindings.Invo
if err != nil {
return nil, err
}
return nil, nil
}

View File

@ -24,11 +24,13 @@ type MockHelper struct {
func (m *MockHelper) Init(accountName string, accountKey string, queueName string, decodeBase64 bool) error {
retvals := m.Called(accountName, accountKey, queueName, decodeBase64)
return retvals.Error(0)
}
func (m *MockHelper) Write(data []byte, ttl *time.Duration) error {
retvals := m.Called(data, ttl)
return retvals.Error(0)
}
@ -141,9 +143,10 @@ func TestReadQueue(t *testing.T) {
assert.Nil(t, err)
var handler = func(data *bindings.ReadResponse) error {
handler := func(data *bindings.ReadResponse) error {
s := string(data.Data)
assert.Equal(t, s, "This is my message")
return nil
}
@ -175,9 +178,10 @@ func TestReadQueueDecode(t *testing.T) {
assert.Nil(t, err)
var handler = func(data *bindings.ReadResponse) error {
handler := func(data *bindings.ReadResponse) error {
s := string(data.Data)
assert.Equal(t, s, "This is my message")
return nil
}
@ -231,9 +235,10 @@ func TestReadQueueNoMessage(t *testing.T) {
err := a.Init(m)
assert.Nil(t, err)
var handler = func(data *bindings.ReadResponse) error {
handler := func(data *bindings.ReadResponse) error {
s := string(data.Data)
assert.Equal(t, s, "This is my message")
return nil
}

View File

@ -50,6 +50,7 @@ func (b *Binding) Init(metadata bindings.Metadata) error {
return errors.Wrapf(err, "invalid schedule format: %s", s)
}
b.schedule = s
return nil
}
@ -73,6 +74,7 @@ func (b *Binding) Read(handler func(*bindings.ReadResponse) error) error {
<-b.stopCh
b.logger.Debugf("stopping schedule: %s", b.schedule)
c.Stop()
return nil
}
@ -84,6 +86,7 @@ func (b *Binding) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse,
req.Operation, bindings.DeleteOperation)
}
b.stopCh <- true
return &bindings.InvokeResponse{
Metadata: map[string]string{
"schedule": b.schedule,

View File

@ -19,6 +19,7 @@ func getTestMetadata(schedule string) bindings.Metadata {
m.Properties = map[string]string{
"schedule": schedule,
}
return m
}
@ -27,6 +28,7 @@ func getNewCron() *Binding {
if os.Getenv("DEBUG") != "" {
l.SetOutputLevel(logger.DebugLevel)
}
return NewCron(l)
}
@ -70,6 +72,7 @@ func TestCronReadWithDeleteInvoke(t *testing.T) {
assert.Truef(t, exists, "Response metadata doesn't include the expected 'schedule' key")
assert.Equal(t, schedule, scheduleVal)
}
return nil
})
assert.NoErrorf(t, err, "error on read")

View File

@ -63,6 +63,7 @@ func (g *GCPStorage) Init(metadata bindings.Metadata) error {
g.metadata = gm
g.client = client
return nil
}
@ -71,6 +72,7 @@ func (g *GCPStorage) parseMetadata(metadata bindings.Metadata) ([]byte, error) {
if err != nil {
return nil, err
}
return b, nil
}
@ -90,5 +92,6 @@ func (g *GCPStorage) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRespon
if _, err := h.Write(req.Data); err != nil {
return nil, err
}
return nil, nil
}

View File

@ -16,8 +16,10 @@ import (
func TestInit(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{"auth_provider_x509_cert_url": "a", "auth_uri": "a", "Bucket": "a", "client_x509_cert_url": "a", "client_email": "a", "client_id": "a", "private_key": "a",
"private_key_id": "a", "project_id": "a", "token_uri": "a", "type": "a"}
m.Properties = map[string]string{
"auth_provider_x509_cert_url": "a", "auth_uri": "a", "Bucket": "a", "client_x509_cert_url": "a", "client_email": "a", "client_id": "a", "private_key": "a",
"private_key_id": "a", "project_id": "a", "token_uri": "a", "type": "a",
}
gs := GCPStorage{logger: logger.NewLogger("test")}
b, err := gs.parseMetadata(m)
assert.Nil(t, err)

View File

@ -70,11 +70,13 @@ func (g *GCPPubSub) Init(metadata bindings.Metadata) error {
g.client = pubsubClient
g.metadata = &pubsubMeta
return nil
}
func (g *GCPPubSub) parseMetadata(metadata bindings.Metadata) ([]byte, error) {
b, err := json.Marshal(metadata.Properties)
return b, err
}
@ -89,6 +91,7 @@ func (g *GCPPubSub) Read(handler func(*bindings.ReadResponse) error) error {
m.Ack()
}
})
return err
}
@ -107,5 +110,6 @@ func (g *GCPPubSub) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRespons
_, err := t.Publish(ctx, &pubsub.Message{
Data: req.Data,
}).Get(ctx)
return nil, err
}

View File

@ -16,8 +16,10 @@ import (
func TestInit(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{"auth_provider_x509_cert_url": "https://auth", "auth_uri": "https://auth", "client_x509_cert_url": "https://cert", "client_email": "test@test.com", "client_id": "id", "private_key": "****",
"private_key_id": "key_id", "project_id": "project1", "token_uri": "https://token", "type": "serviceaccount", "topic": "t1", "subscription": "s1"}
m.Properties = map[string]string{
"auth_provider_x509_cert_url": "https://auth", "auth_uri": "https://auth", "client_x509_cert_url": "https://cert", "client_email": "test@test.com", "client_id": "id", "private_key": "****",
"private_key_id": "key_id", "project_id": "project1", "token_uri": "https://token", "type": "serviceaccount", "topic": "t1", "subscription": "s1",
}
ps := GCPPubSub{logger: logger.NewLogger("test")}
b, err := ps.parseMetadata(m)
assert.Nil(t, err)

View File

@ -48,11 +48,13 @@ func (h *HTTPSource) Init(metadata bindings.Metadata) error {
}
h.metadata = m
return nil
}
func (h *HTTPSource) get(url string) ([]byte, error) {
client := http.Client{Timeout: time.Second * 60}
// nolint: noctx
resp, err := client.Get(url)
if err != nil {
return nil, err
@ -66,6 +68,7 @@ func (h *HTTPSource) get(url string) ([]byte, error) {
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
return b, nil
}
@ -78,6 +81,7 @@ func (h *HTTPSource) Read(handler func(*bindings.ReadResponse) error) error {
handler(&bindings.ReadResponse{
Data: b,
})
return nil
}
@ -87,6 +91,7 @@ func (h *HTTPSource) Operations() []bindings.OperationKind {
func (h *HTTPSource) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
client := http.Client{Timeout: time.Second * 5}
// nolint: noctx
resp, err := client.Post(h.metadata.URL, "application/json; charset=utf-8", bytes.NewBuffer(req.Data))
if err != nil {
return nil, err
@ -94,5 +99,6 @@ func (h *HTTPSource) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRespon
if resp != nil && resp.Body != nil {
resp.Body.Close()
}
return nil, nil
}

View File

@ -64,6 +64,7 @@ func (i *Influx) Init(metadata bindings.Metadata) error {
client := influxdb2.NewClient(i.metadata.URL, i.metadata.Token)
i.client = client
i.writeAPI = i.client.WriteAPIBlocking(i.metadata.Org, i.metadata.Bucket)
return nil
}
@ -79,6 +80,7 @@ func (i *Influx) getInfluxMetadata(metadata bindings.Metadata) (*influxMetadata,
if err != nil {
return nil, err
}
return &iMetadata, nil
}
@ -103,5 +105,6 @@ func (i *Influx) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse,
return nil, errors.New("Influx Error: Cannot write point")
}
i.client.Close()
return nil, nil
}

View File

@ -64,11 +64,13 @@ func (consumer *consumer) ConsumeClaim(session sarama.ConsumerGroupSession, clai
}
}
}
return nil
}
func (consumer *consumer) Setup(sarama.ConsumerGroupSession) error {
close(consumer.ready)
return nil
}
@ -96,11 +98,12 @@ func (k *Kafka) Init(metadata bindings.Metadata) error {
k.consumerGroup = meta.ConsumerGroup
k.authRequired = meta.AuthRequired
//ignore SASL properties if authRequired is false
// ignore SASL properties if authRequired is false
if meta.AuthRequired {
k.saslUsername = meta.SaslUsername
k.saslPassword = meta.SaslPassword
}
return nil
}
@ -146,13 +149,12 @@ func (k *Kafka) getKafkaMetadata(metadata bindings.Metadata) (*kafkaMetadata, er
return nil, errors.New("kafka error: 'authRequired' attribute was empty")
}
validAuthRequired, err := strconv.ParseBool(val)
if err != nil {
return nil, errors.New("kafka error: invalid value for 'authRequired' attribute")
}
meta.AuthRequired = validAuthRequired
//ignore SASL properties if authRequired is false
// ignore SASL properties if authRequired is false
if meta.AuthRequired {
if val, ok := metadata.Properties["saslUsername"]; ok && val != "" {
meta.SaslUsername = val
@ -166,6 +168,7 @@ func (k *Kafka) getKafkaMetadata(metadata bindings.Metadata) (*kafkaMetadata, er
return nil, errors.New("kafka error: missing SASL Password")
}
}
return &meta, nil
}
@ -176,7 +179,7 @@ func (k *Kafka) getSyncProducer(meta *kafkaMetadata) (sarama.SyncProducer, error
config.Producer.Return.Successes = true
config.Version = sarama.V1_0_0_0
//ignore SASL properties if authRequired is false
// ignore SASL properties if authRequired is false
if meta.AuthRequired {
updateAuthInfo(config, meta.SaslUsername, meta.SaslPassword)
}
@ -185,13 +188,14 @@ func (k *Kafka) getSyncProducer(meta *kafkaMetadata) (sarama.SyncProducer, error
if err != nil {
return nil, err
}
return producer, nil
}
func (k *Kafka) Read(handler func(*bindings.ReadResponse) error) error {
config := sarama.NewConfig()
config.Version = sarama.V1_0_0_0
//ignore SASL properties if authRequired is false
// ignore SASL properties if authRequired is false
if k.authRequired {
updateAuthInfo(config, k.saslUsername, k.saslPassword)
}
@ -233,12 +237,14 @@ func (k *Kafka) Read(handler func(*bindings.ReadResponse) error) error {
if err = client.Close(); err != nil {
return err
}
return nil
}
func (consumer *consumer) Cleanup(sarama.ConsumerGroupSession) error {
return nil
}
func updateAuthInfo(config *sarama.Config, saslUsername, saslPassword string) {
config.Net.SASL.Enable = true
config.Net.SASL.User = saslUsername
@ -246,8 +252,9 @@ func updateAuthInfo(config *sarama.Config, saslUsername, saslPassword string) {
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.TLS.Enable = true
// nolint: gosec
config.Net.TLS.Config = &tls.Config{
//InsecureSkipVerify: true,
// InsecureSkipVerify: true,
ClientAuth: 0,
}
}

View File

@ -15,7 +15,6 @@ import (
"time"
kubeclient "github.com/dapr/components-contrib/authentication/kubernetes"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
v1 "k8s.io/api/core/v1"
@ -50,6 +49,7 @@ func (k *kubernetesInput) Init(metadata bindings.Metadata) error {
return err
}
k.kubeClient = client
return k.parseMetadata(metadata)
}
@ -68,6 +68,7 @@ func (k *kubernetesInput) parseMetadata(metadata bindings.Metadata) error {
k.resyncPeriodInSec = time.Second * time.Duration(intval)
}
}
return nil
}
@ -139,5 +140,6 @@ func (k *kubernetesInput) Read(handler func(*bindings.ReadResponse) error) error
close(stopCh)
}
}
return nil
}

View File

@ -17,7 +17,6 @@ import (
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
)
@ -66,6 +65,7 @@ func (m *MQTT) Init(metadata bindings.Metadata) error {
return err
}
m.client = client
return nil
}
@ -80,6 +80,7 @@ func (m *MQTT) getMQTTMetadata(metadata bindings.Metadata) (*mqttMetadata, error
if err != nil {
return nil, err
}
return &mMetadata, nil
}
@ -90,6 +91,7 @@ func (m *MQTT) Operations() []bindings.OperationKind {
func (m *MQTT) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, error) {
m.client.Publish(m.metadata.Topic, 0, false, string(req.Data))
m.client.Disconnect(0)
return nil, nil
}
@ -103,6 +105,7 @@ func (m *MQTT) Read(handler func(*bindings.ReadResponse) error) error {
})
})
<-c
return nil
}
@ -115,6 +118,7 @@ func (m *MQTT) connect(clientID string, uri *url.URL) (mqtt.Client, error) {
if err := token.Error(); err != nil {
return nil, err
}
return client, nil
}
@ -125,5 +129,6 @@ func (m *MQTT) createClientOptions(clientID string, uri *url.URL) *mqtt.ClientOp
password, _ := uri.User.Password()
opts.SetPassword(password)
opts.SetClientID(clientID)
return opts
}

View File

@ -0,0 +1,167 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package postgres
import (
"context"
"encoding/json"
"strconv"
"time"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/jackc/pgx/v4/pgxpool"
"github.com/pkg/errors"
)
// List of operations.
const (
execOperation bindings.OperationKind = "exec"
queryOperation bindings.OperationKind = "query"
closeOperation bindings.OperationKind = "close"
connectionURLKey = "url"
commandSQLKey = "sql"
)
// Postgres represents PostgreSQL output binding
type Postgres struct {
logger logger.Logger
db *pgxpool.Pool
}
var _ = bindings.OutputBinding(&Postgres{})
// NewPostgres returns a new PostgreSQL output binding
func NewPostgres(logger logger.Logger) *Postgres {
return &Postgres{logger: logger}
}
// Init initializes the PostgreSql binding
func (p *Postgres) Init(metadata bindings.Metadata) error {
url, ok := metadata.Properties[connectionURLKey]
if !ok || url == "" {
return errors.Errorf("required metadata not set: %s", connectionURLKey)
}
poolConfig, err := pgxpool.ParseConfig(url)
if err != nil {
return errors.Wrap(err, "error opening DB connection")
}
p.db, err = pgxpool.ConnectConfig(context.Background(), poolConfig)
if err != nil {
return errors.Wrap(err, "unable to ping the DB")
}
return nil
}
// Operations returns list of operations supported by PostgreSql binding
func (p *Postgres) Operations() []bindings.OperationKind {
return []bindings.OperationKind{
execOperation,
queryOperation,
closeOperation,
}
}
// Invoke handles all invoke operations
func (p *Postgres) Invoke(req *bindings.InvokeRequest) (resp *bindings.InvokeResponse, err error) {
if req == nil {
return nil, errors.Errorf("invoke request required")
}
if req.Operation == closeOperation {
p.db.Close()
return nil, nil
}
if req.Metadata == nil {
return nil, errors.Errorf("metadata required")
}
p.logger.Debugf("operation: %v", req.Operation)
sql, ok := req.Metadata[commandSQLKey]
if !ok || sql == "" {
return nil, errors.Errorf("required metadata not set: %s", commandSQLKey)
}
startTime := time.Now().UTC()
resp = &bindings.InvokeResponse{
Metadata: map[string]string{
"operation": string(req.Operation),
"sql": sql,
"start-time": startTime.Format(time.RFC3339Nano),
},
}
switch req.Operation { // nolint: exhaustive
case execOperation:
r, err := p.exec(sql)
if err != nil {
return nil, errors.Wrapf(err, "error executing %s with %v", sql, err)
}
resp.Metadata["rows-affected"] = strconv.FormatInt(r, 10) // 0 if error
case queryOperation:
d, err := p.query(sql)
if err != nil {
return nil, errors.Wrapf(err, "error executing %s with %v", sql, err)
}
resp.Data = d
default:
return nil, errors.Errorf(
"invalid operation type: %s. Expected %s, %s, or %s",
req.Operation, execOperation, queryOperation, closeOperation,
)
}
endTime := time.Now().UTC()
resp.Metadata["end-time"] = endTime.Format(time.RFC3339Nano)
resp.Metadata["duration"] = endTime.Sub(startTime).String()
return resp, nil
}
func (p *Postgres) query(sql string) (result []byte, err error) {
p.logger.Debugf("query: %s", sql)
rows, err := p.db.Query(context.Background(), sql)
if err != nil {
return nil, errors.Wrapf(err, "error executing %s", sql)
}
rs := make([]interface{}, 0)
for rows.Next() {
val, rowErr := rows.Values()
if rowErr != nil {
return nil, errors.Wrapf(rowErr, "error parsing result: %v", rows.Err())
}
rs = append(rs, val)
}
if result, err = json.Marshal(rs); err != nil {
err = errors.Wrap(err, "error serializing results")
}
return
}
func (p *Postgres) exec(sql string) (result int64, err error) {
p.logger.Debugf("exec: %s", sql)
res, err := p.db.Exec(context.Background(), sql)
if err != nil {
return 0, errors.Wrapf(err, "error executing %s", sql)
}
result = res.RowsAffected()
return
}

View File

@ -0,0 +1,121 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package postgres
import (
"fmt"
"os"
"testing"
"time"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/stretchr/testify/assert"
)
const (
testTableDDL = `CREATE TABLE IF NOT EXISTS foo (
id bigint NOT NULL,
v1 character varying(50) NOT NULL,
ts TIMESTAMP)`
testInsert = "INSERT INTO foo (id, v1, ts) VALUES (%d, 'test-%d', '%v')"
testDelete = "DELETE FROM foo"
testUpdate = "UPDATE foo SET ts = '%v' WHERE id = %d"
testSelect = "SELECT * FROM foo WHERE id < 3"
)
func TestOperations(t *testing.T) {
t.Parallel()
t.Run("Get operation list", func(t *testing.T) {
b := NewPostgres(nil)
assert.NotNil(t, b)
l := b.Operations()
assert.Equal(t, 3, len(l))
})
}
// SETUP TESTS
// 1. `createdb daprtest`
// 2. `createuser daprtest`
// 3. `psql=# grant all privileges on database daprtest to daprtest;``
// 4. `export POSTGRES_TEST_CONN_URL="postgres://daprtest@localhost:5432/daprtest"``
// 5. `go test -v -count=1 ./bindings/postgres -run ^TestPostgresIntegration`
func TestPostgresIntegration(t *testing.T) {
url := os.Getenv("POSTGRES_TEST_CONN_URL")
if url == "" {
t.SkipNow()
}
// live DB test
b := NewPostgres(logger.NewLogger("test"))
m := bindings.Metadata{Properties: map[string]string{connectionURLKey: url}}
if err := b.Init(m); err != nil {
t.Fatal(err)
}
// create table
req := &bindings.InvokeRequest{
Operation: execOperation,
Metadata: map[string]string{commandSQLKey: testTableDDL},
}
t.Run("Invoke create table", func(t *testing.T) {
res, err := b.Invoke(req)
assertResponse(t, res, err)
})
t.Run("Invoke delete", func(t *testing.T) {
req.Metadata[commandSQLKey] = testDelete
res, err := b.Invoke(req)
assertResponse(t, res, err)
})
t.Run("Invoke insert", func(t *testing.T) {
for i := 0; i < 10; i++ {
req.Metadata[commandSQLKey] = fmt.Sprintf(testInsert, i, i, time.Now().Format(time.RFC3339))
res, err := b.Invoke(req)
assertResponse(t, res, err)
}
})
t.Run("Invoke update", func(t *testing.T) {
for i := 0; i < 10; i++ {
req.Metadata[commandSQLKey] = fmt.Sprintf(testUpdate, time.Now().Format(time.RFC3339), i)
res, err := b.Invoke(req)
assertResponse(t, res, err)
}
})
t.Run("Invoke select", func(t *testing.T) {
req.Operation = queryOperation
req.Metadata[commandSQLKey] = testSelect
res, err := b.Invoke(req)
assertResponse(t, res, err)
})
t.Run("Invoke delete", func(t *testing.T) {
req.Operation = execOperation
req.Metadata[commandSQLKey] = testDelete
req.Data = nil
res, err := b.Invoke(req)
assertResponse(t, res, err)
})
t.Run("Invoke close", func(t *testing.T) {
req.Operation = closeOperation
req.Metadata = nil
req.Data = nil
_, err := b.Invoke(req)
assert.NoError(t, err)
})
}
func assertResponse(t *testing.T, res *bindings.InvokeResponse, err error) {
assert.NoError(t, err)
assert.NotNil(t, res)
assert.NotNil(t, res.Metadata)
}

View File

@ -6,7 +6,8 @@
package rabbitmq
import (
"encoding/json"
"errors"
"fmt"
"strconv"
"time"
@ -16,7 +17,14 @@ import (
)
const (
host = "host"
queueName = "queueName"
durable = "durable"
deleteWhenUnused = "deleteWhenUnused"
prefetchCount = "prefetchCount"
rabbitMQQueueMessageTTLKey = "x-message-ttl"
defaultBase = 10
defaultBitSize = 0
)
// RabbitMQ allows sending/receiving data to/from RabbitMQ
@ -30,10 +38,11 @@ type RabbitMQ struct {
// Metadata is the rabbitmq config
type rabbitMQMetadata struct {
QueueName string `json:"queueName"`
Host string `json:"host"`
QueueName string `json:"queueName"`
Durable bool `json:"durable,string"`
DeleteWhenUnused bool `json:"deleteWhenUnused,string"`
PrefetchCount int `json:"prefetchCount"`
defaultQueueTTL *time.Duration
}
@ -58,7 +67,7 @@ func (r *RabbitMQ) Init(metadata bindings.Metadata) error {
if err != nil {
return err
}
ch.Qos(r.metadata.PrefetchCount, 0, true)
r.connection = conn
r.channel = ch
@ -105,15 +114,42 @@ func (r *RabbitMQ) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse
}
func (r *RabbitMQ) parseMetadata(metadata bindings.Metadata) error {
b, err := json.Marshal(metadata.Properties)
if err != nil {
return err
m := rabbitMQMetadata{}
if val, ok := metadata.Properties[host]; ok && val != "" {
m.Host = val
} else {
return errors.New("rabbitMQ binding error: missing host address")
}
var m rabbitMQMetadata
err = json.Unmarshal(b, &m)
if err != nil {
return err
if val, ok := metadata.Properties[queueName]; ok && val != "" {
m.QueueName = val
} else {
return errors.New("rabbitMQ binding error: missing queue Name")
}
if val, ok := metadata.Properties[durable]; ok && val != "" {
d, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("rabbitMQ binding error: can't parse durable field: %s", err)
}
m.Durable = d
}
if val, ok := metadata.Properties[deleteWhenUnused]; ok && val != "" {
d, err := strconv.ParseBool(val)
if err != nil {
return fmt.Errorf("rabbitMQ binding error: can't parse deleteWhenUnused field: %s", err)
}
m.DeleteWhenUnused = d
}
if val, ok := metadata.Properties[prefetchCount]; ok && val != "" {
parsedVal, err := strconv.ParseInt(val, defaultBase, defaultBitSize)
if err != nil {
return fmt.Errorf("rabbitMQ binding error: can't parse prefetchCount field: %s", err)
}
m.PrefetchCount = int(parsedVal)
}
ttl, ok, err := bindings.TryGetTTL(metadata.Properties)
@ -126,6 +162,7 @@ func (r *RabbitMQ) parseMetadata(metadata bindings.Metadata) error {
}
r.metadata = m
return nil
}
@ -168,5 +205,6 @@ func (r *RabbitMQ) Read(handler func(*bindings.ReadResponse) error) error {
}()
<-forever
return nil
}

View File

@ -25,32 +25,40 @@ func TestParseMetadata(t *testing.T) {
expectedDeleteWhenUnused bool
expectedDurable bool
expectedTTL *time.Duration
expectedPrefetchCount int
}{
{
name: "Delete / Durable",
properties: map[string]string{"QueueName": queueName, "Host": host, "DeleteWhenUnused": "true", "Durable": "true"},
properties: map[string]string{"queueName": queueName, "host": host, "deleteWhenUnused": "true", "durable": "true"},
expectedDeleteWhenUnused: true,
expectedDurable: true,
},
{
name: "Not Delete / Not Durable",
properties: map[string]string{"QueueName": queueName, "Host": host, "DeleteWhenUnused": "false", "Durable": "false"},
name: "Not Delete / Not durable",
properties: map[string]string{"queueName": queueName, "host": host, "deleteWhenUnused": "false", "durable": "false"},
expectedDeleteWhenUnused: false,
expectedDurable: false,
},
{
name: "With one second TTL",
properties: map[string]string{"QueueName": queueName, "Host": host, "DeleteWhenUnused": "false", "Durable": "false", bindings.TTLMetadataKey: "1"},
properties: map[string]string{"queueName": queueName, "host": host, "deleteWhenUnused": "false", "durable": "false", bindings.TTLMetadataKey: "1"},
expectedDeleteWhenUnused: false,
expectedDurable: false,
expectedTTL: &oneSecondTTL,
},
{
name: "Empty TTL",
properties: map[string]string{"QueueName": queueName, "Host": host, "DeleteWhenUnused": "false", "Durable": "false", bindings.TTLMetadataKey: ""},
properties: map[string]string{"queueName": queueName, "host": host, "deleteWhenUnused": "false", "durable": "false", bindings.TTLMetadataKey: ""},
expectedDeleteWhenUnused: false,
expectedDurable: false,
},
{
name: "With one prefetchCount",
properties: map[string]string{"queueName": queueName, "host": host, "deleteWhenUnused": "false", "durable": "false", "prefetchCount": "1"},
expectedDeleteWhenUnused: false,
expectedDurable: false,
expectedPrefetchCount: 1,
},
}
for _, tt := range testCases {
@ -65,6 +73,7 @@ func TestParseMetadata(t *testing.T) {
assert.Equal(t, tt.expectedDeleteWhenUnused, r.metadata.DeleteWhenUnused)
assert.Equal(t, tt.expectedDurable, r.metadata.Durable)
assert.Equal(t, tt.expectedTTL, r.metadata.defaultQueueTTL)
assert.Equal(t, tt.expectedPrefetchCount, r.metadata.PrefetchCount)
})
}
}
@ -79,15 +88,15 @@ func TestParseMetadataWithInvalidTTL(t *testing.T) {
}{
{
name: "Whitespaces TTL",
properties: map[string]string{"QueueName": queueName, "Host": host, bindings.TTLMetadataKey: " "},
properties: map[string]string{"queueName": queueName, "host": host, bindings.TTLMetadataKey: " "},
},
{
name: "Negative ttl",
properties: map[string]string{"QueueName": queueName, "Host": host, bindings.TTLMetadataKey: "-1"},
properties: map[string]string{"queueName": queueName, "host": host, bindings.TTLMetadataKey: "-1"},
},
{
name: "Non-numeric ttl",
properties: map[string]string{"QueueName": queueName, "Host": host, bindings.TTLMetadataKey: "abc"},
properties: map[string]string{"queueName": queueName, "host": host, bindings.TTLMetadataKey: "abc"},
},
}

View File

@ -15,7 +15,6 @@ import (
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
redis "github.com/go-redis/redis/v7"
)
@ -129,7 +128,9 @@ func (r *Redis) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, e
if err != nil {
return nil, err
}
return nil, nil
}
return nil, errors.New("redis binding: missing key on write request metadata")
}

View File

@ -0,0 +1,190 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package statechange
import (
"encoding/json"
"strconv"
"strings"
"time"
r "github.com/dancannon/gorethink"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/pkg/errors"
)
// Binding represents RethinkDB change change state input binding which fires handler with
// both the previous and current state store content each time there is a change.
type Binding struct {
logger logger.Logger
session *r.Session
config StateConfig
stopCh chan bool
}
// StateConfig is the binding config
type StateConfig struct {
r.ConnectOpts
Table string `json:"table"`
}
var _ = bindings.InputBinding(&Binding{})
// NewRethinkDBStateChangeBinding returns a new RethinkDB actor event input binding
func NewRethinkDBStateChangeBinding(logger logger.Logger) *Binding {
return &Binding{
logger: logger,
stopCh: make(chan bool),
}
}
// Init initializes the RethinkDB binding
func (b *Binding) Init(metadata bindings.Metadata) error {
cfg, err := metadataToConfig(metadata.Properties, b.logger)
if err != nil {
return errors.Wrap(err, "unable to parse metadata properties")
}
b.config = cfg
ses, err := r.Connect(b.config.ConnectOpts)
if err != nil {
return errors.Wrap(err, "error connecting to the database")
}
b.session = ses
return nil
}
// Read triggers the RethinkDB scheduler
func (b *Binding) Read(handler func(*bindings.ReadResponse) error) error {
b.logger.Infof("subscribing to state changes in %s.%s...", b.config.Database, b.config.Table)
cursor, err := r.DB(b.config.Database).Table(b.config.Table).Changes(r.ChangesOpts{
IncludeTypes: true,
}).Run(b.session)
if err != nil {
errors.Wrapf(err, "error connecting to table %s", b.config.Table)
}
go func() {
for {
var change interface{}
ok := cursor.Next(&change)
if !ok {
b.logger.Errorf("error detecting change: %v", cursor.Err())
break
}
data, err := json.Marshal(change)
if err != nil {
b.logger.Errorf("error marshalling change handler: %v", err)
}
b.logger.Debugf("event: %s", string(data))
resp := &bindings.ReadResponse{
Data: data,
Metadata: map[string]string{
"store-address": b.config.Address,
"store-database": b.config.Database,
"store-table": b.config.Table,
},
}
if err := handler(resp); err != nil {
b.logger.Errorf("error invoking change handler: %v", err)
continue
}
}
}()
done := <-b.stopCh
b.logger.Errorf("done: %b", done)
defer cursor.Close()
return nil
}
func metadataToConfig(cfg map[string]string, logger logger.Logger) (StateConfig, error) {
c := StateConfig{}
for k, v := range cfg {
switch k {
case "address": // string
c.Address = v
case "addresses": // []string
c.Addresses = strings.Split(v, ",")
case "database": // string
c.Database = v
case "username": // string
c.Username = v
case "password": // string
c.Password = v
case "authkey": // string
c.AuthKey = v
case "table": // string
c.Table = v
case "timeout": // time.Duration
d, err := time.ParseDuration(v)
if err != nil {
return c, errors.Wrapf(err, "invalid timeout format: %v", v)
}
c.Timeout = d
case "write_timeout": // time.Duration
d, err := time.ParseDuration(v)
if err != nil {
return c, errors.Wrapf(err, "invalid write timeout format: %v", v)
}
c.WriteTimeout = d
case "read_timeout": // time.Duration
d, err := time.ParseDuration(v)
if err != nil {
return c, errors.Wrapf(err, "invalid read timeout format: %v", v)
}
c.ReadTimeout = d
case "keep_alive_timeout": // time.Duration
d, err := time.ParseDuration(v)
if err != nil {
return c, errors.Wrapf(err, "invalid keep alive timeout format: %v", v)
}
c.KeepAlivePeriod = d
case "initial_cap": // int
i, err := strconv.Atoi(v)
if err != nil {
return c, errors.Wrapf(err, "invalid keep initial cap format: %v", v)
}
c.InitialCap = i
case "max_open": // int
i, err := strconv.Atoi(v)
if err != nil {
return c, errors.Wrapf(err, "invalid keep max open format: %v", v)
}
c.MaxOpen = i
case "discover_hosts": // bool
b, err := strconv.ParseBool(v)
if err != nil {
return c, errors.Wrapf(err, "invalid discover hosts format: %v", v)
}
c.DiscoverHosts = b
case "use-open-tracing": // bool
b, err := strconv.ParseBool(v)
if err != nil {
return c, errors.Wrapf(err, "invalid use open tracing format: %v", v)
}
c.UseOpentracing = b
case "max_idle": // int
i, err := strconv.Atoi(v)
if err != nil {
return c, errors.Wrapf(err, "invalid keep max idle format: %v", v)
}
c.InitialCap = i
default:
logger.Infof("unrecognized metadata: %s", k)
}
}
return c, nil
}

View File

@ -0,0 +1,82 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// ------------------------------------------------------------
package statechange
import (
"os"
"testing"
"time"
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/stretchr/testify/assert"
)
func getTestMetadata() map[string]string {
return map[string]string{
"address": "127.0.0.1:28015",
"database": "dapr",
"username": "admin",
"password": "rethinkdb",
"table": "daprstate",
}
}
func getNewRethinkActorBinding() *Binding {
l := logger.NewLogger("test")
if os.Getenv("DEBUG") != "" {
l.SetOutputLevel(logger.DebugLevel)
}
return NewRethinkDBStateChangeBinding(l)
}
/*
go test github.com/dapr/components-contrib/bindings/rethinkdb/statechange \
-run ^TestBinding$ -count 1
*/
func TestBinding(t *testing.T) {
if os.Getenv("RUN_LIVE_RETHINKDB_TEST") != "true" {
t.SkipNow()
}
testDuration := 10 * time.Second
testDurationStr := os.Getenv("RETHINKDB_TEST_DURATION")
if testDurationStr != "" {
d, err := time.ParseDuration(testDurationStr)
if err != nil {
t.Fatalf("invalid test duration: %s, expected time.Duration", testDurationStr)
}
testDuration = d
}
m := bindings.Metadata{
Name: "test",
Properties: getTestMetadata(),
}
assert.NotNil(t, m.Properties)
b := getNewRethinkActorBinding()
err := b.Init(m)
assert.NoErrorf(t, err, "error initializing")
go func() {
err = b.Read(func(res *bindings.ReadResponse) error {
assert.NotNil(t, res)
t.Logf("state change event:\n%s", string(res.Data))
return nil
})
assert.NoErrorf(t, err, "error on read")
}()
testTimer := time.AfterFunc(testDuration, func() {
t.Log("done")
b.stopCh <- true
})
defer testTimer.Stop()
<-b.stopCh
}

View File

@ -79,6 +79,7 @@ func (sg *SendGrid) Init(metadata bindings.Metadata) error {
// Um, yeah that's about it!
sg.metadata = meta
return nil
}
@ -182,5 +183,6 @@ func (sg *SendGrid) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeRespons
}
sg.logger.Info("sent email with SendGrid")
return nil, nil
}

View File

@ -1,6 +1,7 @@
package sms
import (
"context"
"errors"
"fmt"
"net/http"
@ -96,7 +97,7 @@ func (t *SMS) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, err
vDr := *strings.NewReader(v.Encode())
twilioURL := fmt.Sprintf("%s%s/Messages.json", twilioURLBase, t.metadata.accountSid)
httpReq, err := http.NewRequest("POST", twilioURL, &vDr)
httpReq, err := http.NewRequestWithContext(context.Background(), "POST", twilioURL, &vDr)
if err != nil {
return nil, err
}
@ -112,5 +113,6 @@ func (t *SMS) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse, err
if !(resp.StatusCode >= 200 && resp.StatusCode < 300) {
return nil, fmt.Errorf("error from Twilio: %s", resp.Status)
}
return nil, nil
}

View File

@ -33,6 +33,7 @@ func (t *mockTransport) reset() {
func (t *mockTransport) RoundTrip(req *http.Request) (*http.Response, error) {
atomic.AddInt32(&t.requestCount, 1)
t.request = req
return t.response, t.errToReturn
}
@ -46,8 +47,10 @@ func TestInit(t *testing.T) {
func TestParseDuration(t *testing.T) {
m := bindings.Metadata{}
m.Properties = map[string]string{"toNumber": "toNumber", "fromNumber": "fromNumber",
"accountSid": "accountSid", "authToken": "authToken", "timeout": "badtimeout"}
m.Properties = map[string]string{
"toNumber": "toNumber", "fromNumber": "fromNumber",
"accountSid": "accountSid", "authToken": "authToken", "timeout": "badtimeout",
}
tw := NewSMS(logger.NewLogger("test"))
err := tw.Init(m)
assert.NotNil(t, err)
@ -58,8 +61,10 @@ func TestWriteShouldSucceed(t *testing.T) {
response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))},
}
m := bindings.Metadata{}
m.Properties = map[string]string{"toNumber": "toNumber", "fromNumber": "fromNumber",
"accountSid": "accountSid", "authToken": "authToken"}
m.Properties = map[string]string{
"toNumber": "toNumber", "fromNumber": "fromNumber",
"accountSid": "accountSid", "authToken": "authToken",
}
tw := NewSMS(logger.NewLogger("test"))
tw.httpClient = &http.Client{
Transport: httpTransport,
@ -93,8 +98,10 @@ func TestWriteShouldFail(t *testing.T) {
response: &http.Response{StatusCode: 200, Body: ioutil.NopCloser(strings.NewReader(""))},
}
m := bindings.Metadata{}
m.Properties = map[string]string{"fromNumber": "fromNumber",
"accountSid": "accountSid", "authToken": "authToken"}
m.Properties = map[string]string{
"fromNumber": "fromNumber",
"accountSid": "accountSid", "authToken": "authToken",
}
tw := NewSMS(logger.NewLogger("test"))
tw.httpClient = &http.Client{
Transport: httpTransport,

View File

@ -16,10 +16,9 @@ import (
"github.com/dapr/components-contrib/bindings"
"github.com/dapr/dapr/pkg/logger"
"github.com/pkg/errors"
"github.com/dghubble/go-twitter/twitter"
"github.com/dghubble/oauth1"
"github.com/pkg/errors"
)
// Binding represents Twitter input/output binding
@ -67,6 +66,7 @@ func (t *Binding) Init(metadata bindings.Metadata) error {
httpClient := config.Client(oauth1.NoContext, token)
t.client = twitter.NewClient(httpClient)
return nil
}
@ -87,6 +87,7 @@ func (t *Binding) Read(handler func(*bindings.ReadResponse) error) error {
data, marshalErr := json.Marshal(tweet)
if marshalErr != nil {
t.logger.Errorf("error marshaling tweet: %+v", tweet)
return
}
handler(&bindings.ReadResponse{
@ -139,6 +140,7 @@ func (t *Binding) Read(handler func(*bindings.ReadResponse) error) error {
done = true
}
}
return nil
}
@ -199,6 +201,7 @@ func (t *Binding) Invoke(req *bindings.InvokeRequest) (*bindings.InvokeResponse,
data, marshalErr := json.Marshal(search.Statuses)
if marshalErr != nil {
t.logger.Errorf("error marshaling tweet: %v", marshalErr)
return nil, errors.Wrapf(err, "error parsing response from: %+v", sq)
}

View File

@ -31,6 +31,7 @@ func getTestMetadata() bindings.Metadata {
"accessToken": testTwitterAccessToken,
"accessSecret": testTwitterAccessSecret,
}
return m
}
@ -62,6 +63,7 @@ func TestReadError(t *testing.T) {
tw.Read(func(res *bindings.ReadResponse) error {
t.Logf("result: %+v", res)
assert.NotNilf(t, err, "no error on read with invalid credentials")
return nil
})
}
@ -89,6 +91,7 @@ func TestReed(t *testing.T) {
json.Unmarshal(res.Data, &tweet)
assert.NotEmpty(t, tweet.IDStr, "tweet should have an ID")
os.Exit(0)
return nil
})
assert.Nilf(t, err, "error on read")

View File

@ -31,13 +31,13 @@ git clone https://github.com/dapr/components-contrib.git github.com/dapr/compone
| Type | Directory | Reference | Docs |
|------|-----------|--------------------------|------|
| State | [components-contrib/state](https://github.com/dapr/components-contrib/tree/master/state) | [Redis](https://github.com/dapr/components-contrib/tree/master/state/redis) | [concept](https://github.com/dapr/docs/blob/master/concepts/state-management), [howto](https://github.com/dapr/docs/tree/master/howto/setup-state-store), [api spec](https://github.com/dapr/docs/blob/master/reference/api/state_api.md) |
| Pubsub | [components-contrib/pubsub](https://github.com/dapr/components-contrib/tree/master/pubsub) | [Redis](https://github.com/dapr/components-contrib/tree/master/pubsub/redis) | [concept](https://github.com/dapr/docs/tree/master/concepts/publish-subscribe-messaging), [howto](https://github.com/dapr/docs/tree/master/howto/setup-pub-sub-message-broker), [api spec](https://github.com/dapr/docs/blob/master/reference/api/pubsub_api.md) |
| Bindings | [components-contrib/bindings](https://github.com/dapr/components-contrib/tree/master/bindings) | [Kafka](https://github.com/dapr/components-contrib/tree/master/bindings/kafka) | [concept](https://github.com/dapr/docs/tree/master/concepts/bindings), [input howto](https://github.com/dapr/docs/tree/master/howto/trigger-app-with-input-binding), [output howto](https://github.com/dapr/docs/tree/master/howto/send-events-with-output-bindings), [api spec](https://github.com/dapr/docs/blob/master/reference/api/bindings_api.md) |
| Secret Store | [components-contrib/secretstore](https://github.com/dapr/components-contrib/tree/master/secretstores) | [Kubernetes](https://github.com/dapr/components-contrib/tree/master/secretstores/kubernetes), [Azure Keyvault](https://github.com/dapr/components-contrib/tree/master/secretstores/azure/keyvault) | [concept](https://github.com/dapr/docs/blob/master/concepts/secrets), [howto](https://github.com/dapr/docs/tree/master/howto/setup-secret-store)|
| Middleware | [components-contrib/middleware](https://github.com/dapr/components-contrib/tree/master/middleware) | [Oauth2](https://github.com/dapr/components-contrib/blob/master/middleware/http/oauth2/oauth2_middleware.go) | [concept](https://github.com/dapr/docs/blob/master/concepts/middleware), [howto](https://github.com/dapr/docs/tree/master/howto/authorization-with-oauth) |
| Exporter | [components-contrib/exporters](https://github.com/dapr/components-contrib/tree/master/exporters) | [Zipkin](https://github.com/dapr/components-contrib/blob/master/exporters/zipkin/zipkin_exporter.go) | [concept](https://github.com/dapr/docs/tree/master/concepts/observability), [howto](https://github.com/dapr/docs/tree/master/howto/diagnose-with-tracing) |
| Service Discovery | [components-contrib/servicediscovery](https://github.com/dapr/components-contrib/tree/master/servicediscovery) | [mdns](https://github.com/dapr/components-contrib/blob/master/servicediscovery/mdns/mdns.go) | [howto](https://github.com/dapr/docs/tree/master/howto/invoke-and-discover-services) |
| State | [components-contrib/state](https://github.com/dapr/components-contrib/tree/master/state) | [Redis](https://github.com/dapr/components-contrib/tree/master/state/redis) | [concept](https://docs.dapr.io/developing-applications/building-blocks/state-management/state-management-overview/), [howto](https://docs.dapr.io/developing-applications/building-blocks/state-management/howto-get-save-state/), [api spec](https://docs.dapr.io/reference/api/state_api/) |
| Pubsub | [components-contrib/pubsub](https://github.com/dapr/components-contrib/tree/master/pubsub) | [Redis](https://github.com/dapr/components-contrib/tree/master/pubsub/redis) | [concept](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-overview/), [howto](https://docs.dapr.io/developing-applications/building-blocks/pubsub/howto-publish-subscribe/), [api spec](https://docs.dapr.io/reference/api/pubsub_api/) |
| Bindings | [components-contrib/bindings](https://github.com/dapr/components-contrib/tree/master/bindings) | [Kafka](https://github.com/dapr/components-contrib/tree/master/bindings/kafka) | [concept](https://docs.dapr.io/developing-applications/building-blocks/bindings/bindings-overview/), [input howto](https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-triggers/), [output howto](https://docs.dapr.io/developing-applications/building-blocks/bindings/howto-bindings/), [api spec](https://docs.dapr.io/reference/api/bindings_api/) |
| Secret Store | [components-contrib/secretstore](https://github.com/dapr/components-contrib/tree/master/secretstores) | [Kubernetes](https://github.com/dapr/components-contrib/tree/master/secretstores/kubernetes), [Azure Keyvault](https://github.com/dapr/components-contrib/tree/master/secretstores/azure/keyvault) | [concept](https://docs.dapr.io/developing-applications/building-blocks/secrets/secrets-overview/), [howto](https://docs.dapr.io/developing-applications/building-blocks/secrets/howto-secrets/)|
| Middleware | [components-contrib/middleware](https://github.com/dapr/components-contrib/tree/master/middleware) | [Oauth2](https://github.com/dapr/components-contrib/blob/master/middleware/http/oauth2/oauth2_middleware.go) | [concept](https://docs.dapr.io/concepts/middleware-concept/), [howto](https://docs.dapr.io/operations/security/oauth/) |
| Exporter | [components-contrib/exporters](https://github.com/dapr/components-contrib/tree/master/exporters) | [Zipkin](https://github.com/dapr/components-contrib/blob/master/exporters/zipkin/zipkin_exporter.go) | [concept](https://docs.dapr.io/concepts/observability-concept/), [howto](https://docs.dapr.io/operations/troubleshooting/setup-tracing/) |
| Service Discovery | [components-contrib/servicediscovery](https://github.com/dapr/components-contrib/tree/master/servicediscovery) | [mdns](https://github.com/dapr/components-contrib/blob/master/servicediscovery/mdns/mdns.go) | [howto](https://docs.dapr.io/developing-applications/building-blocks/service-invocation/howto-invoke-discover-services/) |
### Running unit-test
@ -68,7 +68,7 @@ make DEBUG=1 build
```bash
# Back up the current daprd
mv /usr/local/bin/daprd /usr/local/bin/daprd.bak
cp ./dist/darwin_amd64/debug/daprd /usr/local/bin
cp ./dist/darwin_amd64/debug/daprd ~/.dapr/bin
```
> Linux Debuggable Binary: ./dist/linux_amd64/debug/daprd
> Windows Debuggable Binary: .\dist\windows_amd64\debug\daprd
@ -84,7 +84,8 @@ cp ./dist/darwin_amd64/debug/daprd /usr/local/bin
3. Fetch the latest dapr/dapr repo
4. Update component-contrib go mod and ensure that component-contrib is updated to the latest version
```bash
go get -u github.com/dapr/components-contrib
go get -u github.com/dapr/components-contrib@master
go mod tidy
```
5. Import your component to Dapr [main.go](https://github.com/dapr/dapr/blob/d17e9243b308e830649b0bf3af5f6e84fd543baf/cmd/daprd/main.go#L79)
6. Register your component in Dapr [main.go](https://github.com/dapr/dapr/blob/d17e9243b308e830649b0bf3af5f6e84fd543baf/cmd/daprd/main.go#L153-L226)

View File

@ -8,4 +8,5 @@ package exporters
// Exporter is the interface for tracing exporter wrappers
type Exporter interface {
Init(daprID string, hostAddress string, metadata Metadata) error
Unregister()
}

View File

@ -28,7 +28,8 @@ func NewNativeExporter(logger logger.Logger) *Exporter {
// Exporter is an OpenCensus native exporter
type Exporter struct {
logger logger.Logger
logger logger.Logger
traceExporter trace.Exporter
}
// Init creates a new native endpoint and reporter
@ -43,13 +44,14 @@ func (l *Exporter) Init(daprID string, hostAddress string, metadata exporters.Me
return nil
}
exporter, err := ocagent.NewExporter(ocagent.WithInsecure(), ocagent.WithServiceName(daprID), ocagent.WithAddress(meta.AgentEndpoint))
l.traceExporter, err = ocagent.NewExporter(ocagent.WithInsecure(), ocagent.WithServiceName(daprID), ocagent.WithAddress(meta.AgentEndpoint))
if err != nil {
return err
}
trace.RegisterExporter(exporter)
trace.RegisterExporter(l.traceExporter)
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
return nil
}
@ -64,5 +66,11 @@ func (l *Exporter) getNativeMetadata(metadata exporters.Metadata) (*nativeExport
if err != nil {
return nil, err
}
return &nExporterMetadata, nil
}
// Unregister removes the exporter
func (l *Exporter) Unregister() {
trace.UnregisterExporter(l.traceExporter)
}

View File

@ -39,5 +39,11 @@ func (se *Exporter) Init(daprID string, hostAddress string, metadata exporters.M
se.Buffer = metadata.Buffer
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
trace.RegisterExporter(se)
return nil
}
// Unregister removes the exporter
func (se *Exporter) Unregister() {
trace.UnregisterExporter(se)
}

View File

@ -30,7 +30,8 @@ func NewZipkinExporter(logger logger.Logger) *Exporter {
// Exporter is an OpenCensus zipkin exporter
type Exporter struct {
logger logger.Logger
logger logger.Logger
traceExporter trace.Exporter
}
// Init creates a new zipkin endpoint and reporter
@ -50,9 +51,10 @@ func (z *Exporter) Init(daprID string, hostAddress string, metadata exporters.Me
return err
}
reporter := zipkinHTTP.NewReporter(meta.ExporterAddress)
ze := zipkin.NewExporter(reporter, localEndpoint)
trace.RegisterExporter(ze)
z.traceExporter = zipkin.NewExporter(reporter, localEndpoint)
trace.RegisterExporter(z.traceExporter)
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
return nil
}
@ -67,5 +69,11 @@ func (z *Exporter) getZipkinMetadata(metadata exporters.Metadata) (*zipkinMetada
if err != nil {
return nil, err
}
return &zipkinMeta, nil
}
// Unregister removes the exporter
func (z *Exporter) Unregister() {
trace.UnregisterExporter(z.traceExporter)
}

36
go.mod
View File

@ -3,10 +3,10 @@ module github.com/dapr/components-contrib
go 1.14
require (
cloud.google.com/go v0.52.0
cloud.google.com/go/datastore v1.0.0
cloud.google.com/go/pubsub v1.0.1
cloud.google.com/go/storage v1.0.0
cloud.google.com/go v0.65.0
cloud.google.com/go/datastore v1.1.0
cloud.google.com/go/pubsub v1.3.1
cloud.google.com/go/storage v1.10.0
contrib.go.opencensus.io/exporter/ocagent v0.6.0
contrib.go.opencensus.io/exporter/zipkin v0.1.1
github.com/Azure/azure-event-hubs-go v1.3.1
@ -19,14 +19,16 @@ require (
github.com/Azure/go-autorest/autorest/adal v0.8.3
github.com/Azure/go-autorest/autorest/azure/auth v0.4.2
github.com/Shopify/sarama v1.23.1
github.com/a8m/documentdb v1.2.0
github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905
github.com/aerospike/aerospike-client-go v2.7.0+incompatible
github.com/alicebob/miniredis/v2 v2.13.3
github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible
github.com/apache/pulsar-client-go v0.1.0
github.com/aws/aws-sdk-go v1.25.0
github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f // indirect
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b
github.com/coreos/go-oidc v2.1.0+incompatible
github.com/dancannon/gorethink v4.0.0+incompatible
github.com/dapr/dapr v0.4.1-0.20200228055659-71892bc0111e
github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73
github.com/dghubble/go-twitter v0.0.0-20190719072343-39e5462e111f
@ -37,8 +39,8 @@ require (
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5
github.com/go-redis/redis/v7 v7.0.1
github.com/gocql/gocql v0.0.0-20191018090344-07ace3bab0f8
github.com/golang/mock v1.4.0
github.com/golang/protobuf v1.3.3
github.com/golang/mock v1.4.4
github.com/golang/protobuf v1.4.2
github.com/google/uuid v1.1.1
github.com/grandcat/zeroconf v0.0.0-20190424104450-85eadb44205c
github.com/hashicorp/consul/api v1.2.0
@ -47,12 +49,13 @@ require (
github.com/influxdata/influxdb-client-go v1.4.0
github.com/jackc/pgx/v4 v4.6.0
github.com/json-iterator/go v1.1.8
github.com/lib/pq v1.8.0 // indirect
github.com/mitchellh/mapstructure v1.3.2 // indirect
github.com/nats-io/gnatsd v1.4.1
github.com/nats-io/go-nats v1.7.2
github.com/nats-io/nats-streaming-server v0.17.0 // indirect
github.com/nats-io/nats.go v1.9.1
github.com/nats-io/stan.go v0.6.0
github.com/open-policy-agent/opa v0.23.2
github.com/openzipkin/zipkin-go v0.1.6
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/pkg/errors v0.9.1
@ -65,21 +68,20 @@ require (
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271
github.com/stretchr/testify v1.5.1
github.com/tidwall/pretty v1.0.1 // indirect
github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc // indirect
github.com/valyala/fasthttp v1.6.0
github.com/vmware/vmware-go-kcl v0.0.0-20191104173950-b6c74c3fe74e
go.etcd.io/etcd v3.3.17+incompatible
go.mongodb.org/mongo-driver v1.1.2
go.opencensus.io v0.22.3
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9
golang.org/x/net v0.0.0-20200602114024-627f9648deb9
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d
google.golang.org/api v0.15.0
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150
google.golang.org/grpc v1.26.0
go.opencensus.io v0.22.4
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9
golang.org/x/net v0.0.0-20200822124328-c89045814202
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
google.golang.org/api v0.32.0
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d
google.golang.org/grpc v1.32.0
gopkg.in/couchbase/gocb.v1 v1.6.4
gopkg.in/couchbase/gocbcore.v7 v7.1.16 // indirect
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 // indirect
gopkg.in/gorethink/gorethink.v4 v4.1.0 // indirect
k8s.io/api v0.17.0
k8s.io/apimachinery v0.17.0
k8s.io/client-go v0.17.0

339
go.sum
View File

@ -6,16 +6,40 @@ cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxK
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0 h1:GGslhk/BU052LPlnI1vpp3fcbUs+hQ3E+Doti/3/vF8=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0 h1:Dg9iHVQfrhq82rUNu9ZxUDrJLaxFUe/HlCVaLyRruq8=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go/bigquery v1.0.1 h1:hL+ycaJpVE9M7nLoiXb/Pn10ENE2u+oddxbD8uu0ZVU=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0 h1:Kt+gOPPp2LEPWp8CSfxhsM8ik9CcyE/gYu+0r+RnZvM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0 h1:/May9ojXjRkPBNVrq+oWLqmWCkr4OU5uRY29bu0mRyQ=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1 h1:W9tAK3E57P75u0XLLR82LZyw8VpAnhmyTOxW9qzmyj8=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1 h1:ukjixP1wl0LpnZ6LWtZJ0mX5tBmjp1f8Sqer8Z2OMUU=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0 h1:VV2nUM3wwLLGh9lSABFgZMjInyUbJeaRSE64WuAIQ+4=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
contrib.go.opencensus.io/exporter/ocagent v0.5.0/go.mod h1:ImxhfLRpxoYiSq891pBrLVhN+qmP8BTVvdH2YLs7Gl0=
contrib.go.opencensus.io/exporter/ocagent v0.6.0 h1:Z1n6UAyr0QwM284yUuh5Zd8JlvxUGAhFZcgMJkMPrGM=
contrib.go.opencensus.io/exporter/ocagent v0.6.0/go.mod h1:zmKjrJcdo0aYcVS7bmEeSEBLPA9YJp5bjrofdU3pIXs=
@ -108,6 +132,8 @@ github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798 h1:2T/jmrHeTezcCM58
github.com/DataDog/zstd v1.3.6-0.20190409195224-796139022798/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo=
github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/OneOfOne/xxhash v1.2.7 h1:fzrmmkskv067ZQbd9wERNGuxckWw67dyzoMG62p7LMo=
github.com/OneOfOne/xxhash v1.2.7/go.mod h1:eZbhyaAYD41SGSSsnmcpxVoRiQ/MPUTjUdIIOT9Um7Q=
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
@ -119,6 +145,8 @@ github.com/Shopify/toxiproxy v2.1.4+incompatible h1:TKdv8HiTLgE5wdJuEML90aBgNWso
github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI=
github.com/a8m/documentdb v1.2.0 h1:3ooHoXI6ww5d5Itr39V+bBmX4xm0nKrv0XMKbXw8vwE=
github.com/a8m/documentdb v1.2.0/go.mod h1:4Z0mpi7fkyqjxUdGiNMO3vagyiUoiwLncaIX6AsW5z0=
github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905 h1:lrOYmNobGcyWEjvMIMJERJx1Y4ttPFobY7RHAD+6e10=
github.com/a8m/documentdb v1.2.1-0.20190920062420-efdd52fe0905/go.mod h1:4Z0mpi7fkyqjxUdGiNMO3vagyiUoiwLncaIX6AsW5z0=
github.com/aerospike/aerospike-client-go v2.7.0+incompatible h1:NbjGs0ZMHKge1+0m+XLdhcDm/2gCfjnlasSTtKSr8j4=
github.com/aerospike/aerospike-client-go v2.7.0+incompatible/go.mod h1:zj8LBEnWBDOVEIJt8LvaRvDG5ARAoa5dBeHaB472NRc=
github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU=
@ -127,6 +155,10 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
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/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
github.com/alicebob/miniredis/v2 v2.13.3 h1:kohgdtN58KW/r9ZDVmMJE3MrfbumwsDQStd0LPAGmmw=
github.com/alicebob/miniredis/v2 v2.13.3/go.mod h1:uS970Sw5Gs9/iK3yBg0l9Uj9s25wXxSpQUE9EaJ/Blg=
github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible h1:HXvOJsZw8JT/ldxjX74Aq4H2IY4ojV/mXMDPWFitpv8=
github.com/aliyun/aliyun-oss-go-sdk v2.0.7+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
github.com/apache/pulsar-client-go v0.1.0 h1:2BFZztxtNgFyOzBc+5On84CX6aIZW5xwh7KM0MWigGI=
@ -159,6 +191,7 @@ github.com/bmizerany/perks v0.0.0-20141205001514-d9a9656a3a4b/go.mod h1:ac9efd0D
github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b h1:L/QXpzIa3pOvUGt1D1lA5KjYhPBAN/3iWdP7xeFS9F0=
github.com/bradfitz/gomemcache v0.0.0-20190913173617-a41fca850d0b/go.mod h1:H0wQNHz2YrLsuXOZozoeDmnHXkNCRmMW0gwFWDfEZDA=
github.com/cenkalti/backoff v2.0.0+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@ -175,6 +208,7 @@ github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMn
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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY=
@ -196,6 +230,8 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/dancannon/gorethink v4.0.0+incompatible h1:KFV7Gha3AuqT+gr0B/eKvGhbjmUv0qGF43aKCIKVE9A=
github.com/dancannon/gorethink v4.0.0+incompatible/go.mod h1:BLvkat9KmZc1efyYwhz3WnybhRZtgF1K929FD8z1avU=
github.com/dapr/components-contrib v0.0.0-20200219164914-5b75f4d0fbc6/go.mod h1:AZi8IGs8LFdywJg/YGwDs7MAxJkvGa8RgHN4NoJSKt0=
github.com/dapr/dapr v0.4.1-0.20200228055659-71892bc0111e h1:njRp/SZ/zgqjSDywmy+Dn9oikkZqkqAHWGbfMarUuwo=
github.com/dapr/dapr v0.4.1-0.20200228055659-71892bc0111e/go.mod h1:c60DJ9TdSdpbLjgqP55A5u4ZCYChFwa9UGYIXd9pmm4=
@ -222,7 +258,6 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M=
github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY=
github.com/dimchansky/utfbom v1.0.0 h1:fGC2kkf4qOoKqZ4q7iIh+Vef4ubC1c38UDsEyZynZPc=
github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
github.com/dimchansky/utfbom v1.1.0 h1:FcM3g+nofKgUteL8dm/UpdRXNC9KmADgTpLKsu0TRo4=
github.com/dimchansky/utfbom v1.1.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8=
@ -230,7 +265,6 @@ github.com/dnaeon/go-vcr v1.0.1 h1:r8L/HqC0Hje5AXMu1ooW8oyQyOFv4GxqpL0nRP7SLLY=
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/eapache/go-resiliency v1.1.0 h1:1NtRmCAqadE2FN4ZcN6g90TP3uk8cg9rn9eNK2197aU=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
github.com/eapache/go-resiliency v1.2.0 h1:v7g92e/KSN71Rq7vSThKaWIq68fL4YHvWyiUKorFR1Q=
github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
@ -244,10 +278,11 @@ github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkg
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful v2.10.0+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/evanphx/json-patch v4.2.0+incompatible h1:fUDGZCv/7iAN7u0puUVhvKCcsR6vRfwrJatElLBEf0I=
github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5 h1:M4CVMQ5ueVmGZAtkW2bsO+ftesCYpfxl27JTqtzKBzE=
github.com/fasthttp-contrib/sessions v0.0.0-20160905201309-74f6ac73d5d5/go.mod h1:MQXNGeXkpojWTxbN7vXoE3f7EmlA11MlJbsrJpVBINA=
@ -256,24 +291,25 @@ github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/fortytw2/leaktest v1.2.0 h1:cj6GCiwJDH7l3tMHLjZDo0QqPtrXJiWSI9JgpeQKw+Q=
github.com/fortytw2/leaktest v1.2.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g=
github.com/frankban/quicktest v1.5.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o=
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 h1:0VruCpn7yAIIu7pWVClQC8wxCJEcG3nyzpMSHKi1PQc=
github.com/garyburd/redigo v1.6.0/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/getkin/kin-openapi v0.2.0/go.mod h1:V1z9xl9oF5Wt7v32ne4FmiF1alpS4dM6mNzoywPOXlk=
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v0.0.0-20180820084758-c7ce16629ff4/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-chi/chi v4.0.3+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
@ -294,25 +330,22 @@ github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh
github.com/go-ozzo/ozzo-routing v2.1.4+incompatible/go.mod h1:hvoxy5M9SJaY0viZvcCsODidtUm5CzRbYKEWuQpr+2A=
github.com/go-redis/redis/v7 v7.0.1 h1:AVkqXtvak6eXAvqIA+0rDlh6St/M7/vaf67NEqPhP2w=
github.com/go-redis/redis/v7 v7.0.1/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg=
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 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y=
github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8=
github.com/gocql/gocql v0.0.0-20191018090344-07ace3bab0f8 h1:ZyxBBeTImqFLu9mLtQUnXrO8K/SryXE/xjG/ygl0DxQ=
github.com/gocql/gocql v0.0.0-20191018090344-07ace3bab0f8/go.mod h1:DL0ekTmBSTdlNF25Orwt/JMzqIq3EJ4MVa/J/uK64OY=
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM=
github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.0 h1:G8O7TerXerS4F6sx9OV7/nRfJdnXgHZu/S/7F2SN+UE=
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
@ -322,23 +355,37 @@ github.com/golang/gddo v0.0.0-20190815223733-287de01127ef/go.mod h1:xEhNfoBDX1hz
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/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6 h1:ZgQEtGgCBiWRM39fZuwSd1LwSqqSW0hOdXCYYDX0R3I=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0 h1:Rd1kQnQu0Hq3qvJppYSG0HtP+f5LPPUiDswTLiEegLg=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v0.0.0-20181025225059-d3de96c4c28e/go.mod h1:Qd/q+1AKNOZr9uGQzbzCmRO6sUih6GTPZv6a1/R87v0=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
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/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
@ -349,11 +396,16 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo=
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-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
@ -362,9 +414,15 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0 h1:pMen7vLs8nvgEYhywH3KDWJIJTeEr2ULsVWHWYHQyBs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
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=
@ -372,7 +430,6 @@ github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+Tv3SM=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d h1:7XGaL1e6bYS1yIonGp9761ExpPPV1ui0SAC59Yube9k=
github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY=
github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk=
github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU=
@ -380,9 +437,8 @@ github.com/gophercloud/gophercloud v0.0.0-20190126172459-c818fa66e4c8/go.mod h1:
github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk=
github.com/gorilla/mux v0.0.0-20181024020800-521ea7b17d02/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3 h1:gnP5JzjVOuiZD07fKKToCAOjS0yOpj/qPETTXCCS6hw=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
@ -416,7 +472,6 @@ github.com/hashicorp/go-hclog v0.9.1 h1:9PZfAcVEvez4yhLH2TBU64/h/z4xlFI80cWXRrxu
github.com/hashicorp/go-hclog v0.9.1/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI=
github.com/hashicorp/go-msgpack v0.5.5/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
@ -500,8 +555,8 @@ github.com/jackc/pgx/v4 v4.6.0 h1:Fh0O9GdlG4gYpjpwOqjdEodJUQM9jzN3Hdv7PN0xmm0=
github.com/jackc/pgx/v4 v4.6.0/go.mod h1:vPh43ZzxijXUVJ+t/EmXBtFmbFVO72cuneCT9oAlxAg=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.0 h1:musOWczZC/rSbqut475Vfcczg7jJsdUQf0D6oKPLgNU=
github.com/jackc/puddle v1.1.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
github.com/jcmturner/gofork v1.0.0 h1:J7uCkflzTEhUZ64xqKnkDxq3kzc96ajM1Gli5ktUem8=
github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o=
@ -511,9 +566,7 @@ github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/joomcode/errorx v1.0.0 h1:RJAKLTy1Sv2Tszhu14m5RZP4VGRlhXutG/XlL1En5VM=
github.com/joomcode/errorx v1.0.0/go.mod h1:kgco15ekB6cs+4Xjzo7SPeXzx38PbJzBwbnu9qfVNHQ=
github.com/joomcode/redispipe v0.9.0 h1:NukwwIvxhg6r2lVxa1RJhEZXYPZZF/OX9WZJk+2cK1Q=
github.com/joomcode/redispipe v0.9.0/go.mod h1:4S/gpBCZ62pB/3+XLNWDH7jQnB0vxmpddAMBva2adpM=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7 h1:K//n/AqR5HjG3qxbrBCL4vJPW0MVFSs9CPK1OOJdRME=
github.com/jpillora/backoff v0.0.0-20180909062703-3050d21c67d7/go.mod h1:2iMrUgbbvHEiQClaW2NsSzMyGHqN+rDFqY705q49KG0=
@ -521,11 +574,9 @@ github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBv
github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7 h1:KfgG9LzI+pYjr4xvmz/5H4FXjokeP+rlHLhv3iH62Fo=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.8 h1:QiWkFLKq0T7mpzwOTu6BzNDbfTE8OLrYhVKYMLF46Ok=
github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024 h1:rBMNdlhTLzJjJSDIjNEXX1Pz3Hmwmz91v+zycvx9PJc=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jstemmer/go-junit-report v0.9.1 h1:6QPYqodiu3GuPL+7mfx+NwDdp2eTkp9IfEUpgAwUN0o=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
@ -542,14 +593,11 @@ github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/klauspost/compress v1.8.2 h1:Bx0qjetmNjdFXASH02NSAREKpiaDwkO1DRZ3dV2KCcs=
github.com/klauspost/compress v1.8.2/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.1 h1:vJi+O/nMdFt0vqm8NZBI6wzALWdA2X+egi0ogNyrC/w=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
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 h1:DB17ag19krx9CFsz4o3enTrPXyIXCl+2iCXH/aMAp9s=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@ -561,9 +609,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kubernetes-client/go v0.0.0-20190625181339-cd8e39e789c7 h1:NZlvd1Qf3MwoRhh87iVkJSHK3R31fX3D7kQfdJy6LnQ=
github.com/kubernetes-client/go v0.0.0-20190625181339-cd8e39e789c7/go.mod h1:ks4KCmmxdXksTSu2dlnUanEOqNd/dsoyS6/7bay2RQ8=
github.com/kubernetes-client/go v0.0.0-20190928040339-c757968c4c36 h1:/VKCfQgtQxBXEVU9UAJkW/ybm/070TBG57x2wxYUtXI=
github.com/kubernetes-client/go v0.0.0-20190928040339-c757968c4c36/go.mod h1:ks4KCmmxdXksTSu2dlnUanEOqNd/dsoyS6/7bay2RQ8=
github.com/labstack/echo/v4 v4.1.11 h1:z0BZoArY4FqdpUEl+wlHp4hnr/oSR6MTmQmv8OHSoww=
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
@ -571,10 +617,10 @@ github.com/labstack/gommon v0.3.0 h1:JEeO0bvc78PKdyHxloTKiF8BD5iGrH8T6MSeGvSgob0
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.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 h1:/qkRGz8zljWiDcFvgpwUpwIAPu3r07TDvs3Rws+o/pU=
github.com/lib/pq v1.3.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.8.0 h1:9xohqzkUwzR4Ga4ivdTcawVS89YSDVxXMa3xJX3cGzg=
github.com/lib/pq v1.8.0/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
@ -582,27 +628,23 @@ github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs=
github.com/matryer/moq v0.0.0-20190312154309-6cfb0558e1bd/go.mod h1:9ELz6aaclSIGnZBoaSLZ3NAl1VTufbOrXBPvtcy6WiQ=
github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.2 h1:/bC9yWikZXAL9uJdulbSfyVNIR3n3trXl+v8+1sx8mU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
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-ieproxy v0.0.0-20190610004146-91bb50d98149 h1:HfxbT6/JcvIljmERptWhwa8XzP7H3T+Z2N26gTsaDaA=
github.com/mattn/go-ieproxy v0.0.0-20190610004146-91bb50d98149/go.mod h1:31jz6HNzdxOmlERGGEc4v/dMssOfmp2p5bT/okiKFFc=
github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/mattn/go-isatty v0.0.10 h1:qxFzApOv4WsAL965uUPIsXzAKCZxN2p9UqdhFS4ZW10=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-runewidth v0.0.0-20181025052659-b20a3daf6a39/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/mediocregopher/radix.v2 v0.0.0-20181115013041-b67df6e626f9 h1:ViNuGS149jgnttqhc6XQNPwdupEMBXqCx9wtlW7P3sA=
github.com/mediocregopher/radix.v2 v0.0.0-20181115013041-b67df6e626f9/go.mod h1:fLRUbhbSd5Px2yKUaGYYPltlyxi1guJz1vCmo1RQL50=
github.com/microcosm-cc/bluemonday v1.0.2 h1:5lPfLTTAvAbtS0VqT+94yOtFnGfUWYyx0+iToC3Os3s=
github.com/microcosm-cc/bluemonday v1.0.2/go.mod h1:iVP4YcDBq+n/5fb23BhYFvIMq/leAFZyRl6bYmGDlGc=
@ -610,7 +652,6 @@ github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/minio/blake2b-simd v0.0.0-20160723061019-3f5f724cb5b1/go.mod h1:pD8RvIylQ358TN4wwqatJ8rNavkEINozVn9DtGI3dfQ=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@ -619,7 +660,6 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
@ -640,16 +680,13 @@ github.com/nats-io/gnatsd v1.4.1/go.mod h1:nqco77VO78hLCJpIcVfygDP2rPGfsEHkGTUk9
github.com/nats-io/go-nats v1.7.2 h1:cJujlwCYR8iMz5ofZSD/p2WLW8FabhkQ2lIEVbSvNSA=
github.com/nats-io/go-nats v1.7.2/go.mod h1:+t7RHT5ApZebkrQdnn6AhQJmhJJiKAvJUio1PiiCtj0=
github.com/nats-io/jwt v0.2.14/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.0 h1:xdnzwFETV++jNc4W1mw//qFyJGb2ABOombmZJQS4+Qo=
github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg=
github.com/nats-io/jwt v0.3.2 h1:+RB5hMpXUUA2dfxuhBTEkMOrYmM+gKIZYS1KjSostMI=
github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU=
github.com/nats-io/nats-server/v2 v2.0.4/go.mod h1:AWdGEVbjKRS9ZIx4DSP5eKW48nfFm7q3uiSkP/1KD7M=
github.com/nats-io/nats-server/v2 v2.1.2 h1:i2Ly0B+1+rzNZHHWtD4ZwKi+OU5l+uQo1iDHZ2PmiIc=
github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k=
github.com/nats-io/nats-server/v2 v2.1.4 h1:BILRnsJ2Yb/fefiFbBWADpViGF69uh4sxe8poVDQ06g=
github.com/nats-io/nats-server/v2 v2.1.4/go.mod h1:Jw1Z28soD/QasIA2uWjXyM9El1jly3YwyFOuR8tH1rg=
github.com/nats-io/nats-streaming-server v0.16.2 h1:RyTg8dZ+A8LaDEEmh9BoHFxWJSuSrIGJ4xjsr0fLMeY=
github.com/nats-io/nats-streaming-server v0.16.2/go.mod h1:P12vTqmBpT6Ufs+cu0W1C4N2wmISqa6G4xdLQeO2e2s=
github.com/nats-io/nats-streaming-server v0.17.0 h1:eYhSmjRmRsCYNsoUshmZ+RgKbhq6B+7FvMHXo3M5yMs=
github.com/nats-io/nats-streaming-server v0.17.0/go.mod h1:ewPBEsmp62Znl3dcRsYtlcfwudxHEdYMtYqUQSt4fE0=
@ -657,7 +694,6 @@ github.com/nats-io/nats.go v1.8.1/go.mod h1:BrFz9vVn0fU3AcH9Vn4Kd7W0NpJ651tD5omQ
github.com/nats-io/nats.go v1.9.1 h1:ik3HbLhZ0YABLto7iX80pZLPw/6dx3T+++MZJwLnMrQ=
github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w=
github.com/nats-io/nkeys v0.0.2/go.mod h1:dab7URMsZm6Z/jp9Z5UGa87Uutgc2mVpXLC4B7TDb/4=
github.com/nats-io/nkeys v0.1.0 h1:qMd4+pRHgdr1nAClu+2h/2a5F2TmKcCzjCDazVgRoX4=
github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nkeys v0.1.3 h1:6JrEfig+HzTH85yxzhSVbjHRJv9cn0p6n3IngIcM5/k=
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
@ -667,28 +703,26 @@ github.com/nats-io/stan.go v0.5.0/go.mod h1:dYqB+vMN3C2F9pT1FRQpg9eHbjPj6mP0yYuy
github.com/nats-io/stan.go v0.6.0 h1:26IJPeykh88d8KVLT4jJCIxCyUBOC5/IQup8oWD/QYY=
github.com/nats-io/stan.go v0.6.0/go.mod h1:eIcD5bi3pqbHT/xIIvXMwvzXYElgouBvaVRftaE+eac=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0 h1:Ix8l273rp3QzYgXSR+c8d1fTG7UPgYkOSELPhiY/YGw=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.7.0 h1:WSHQ+IS43OoUrWtD1/bbclrwK8TTH5hzp+umCiuxHgs=
github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.10.2 h1:uqH7bpe+ERSiDa34FDOF7RikN6RzXgduUF8yarlZp94=
github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3 h1:EooPXg51Tn+xmWPXJUGCnJhJSpeuMlBmfJVcqIRmmv8=
github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.4.3 h1:RE1xgDvH7imwFD45h+u2SgIfERHlS2yNG4DObb5BSKU=
github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.7.0 h1:XPnZz8VVBHjVsy1vzJmRwIcSwiUO+JFfrv/xGiigmME=
github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/open-policy-agent/opa v0.23.2 h1:co9fPjnLPwnvaEThBJjCb5E2iAyvW95Qq2PvSOEIwGE=
github.com/open-policy-agent/opa v0.23.2/go.mod h1:rrwxoT/b011T0cyj+gg2VvxqTtn6N3gp/jzmr3fjW44=
github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
github.com/openzipkin/zipkin-go v0.1.1 h1:A/ADD6HaPnAKj3yS7HjGHRK77qi41Hi0DirOOIQAeIw=
github.com/openzipkin/zipkin-go v0.1.1/go.mod h1:NtoC/o8u3JlF1lSlyPNswIbeQH9bJTmOf0Erfk+hxe8=
github.com/openzipkin/zipkin-go v0.1.6 h1:yXiysv1CSK7Q5yjGy1710zZGnsbMUIjluWBxtLXHPBo=
github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
@ -696,12 +730,13 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR
github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
github.com/peterh/liner v0.0.0-20170211195444-bf27d3ba8e1d/go.mod h1:xIteQHvHuaLYG9IFj6mSxM0fCKrs34IrEQUhOYuGPHc=
github.com/phayes/freeport v0.0.0-20171002181615-b8543db493a5/go.mod h1:iIss55rKnNBTvrwdmkUpLnDpZoAHvWaiq5+iMmen4AE=
github.com/pierrec/lz4 v0.0.0-20190327172049-315a67e90e41/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
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.0.0-20181023235946-059132a15dd0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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=
@ -712,6 +747,7 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/prometheus/client_golang v0.0.0-20181025174421-f30f42803563/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
@ -721,16 +757,15 @@ github.com/prometheus/client_golang v1.2.1 h1:JnMpQc6ppsNgw9QPAGF6Dod479itz7lvls
github.com/prometheus/client_golang v1.2.1/go.mod h1:XMU6Z2MjaRKVu/dC1qupJI9SiNkDYzz3xecMgSW/F+U=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/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 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M=
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-20181020173914-7e9e6cabbd39/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY=
github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA=
github.com/prometheus/common v0.9.1 h1:KOMtN28tlbam3/7ZKEYKHhKoJZYYj3gMH4uc62x7X7U=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4=
@ -741,7 +776,6 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.0.0-20190523193104-a7aeb8df3389/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
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 h1:3+auTFlqw+ZaQYJARz6ArODtkaIwtvBTx3N2NehQlL8=
github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
@ -771,7 +805,6 @@ github.com/sendgrid/rest v2.4.1+incompatible h1:HDib/5xzQREPq34lN3YMhQtMkdXxS/qL
github.com/sendgrid/rest v2.4.1+incompatible/go.mod h1:kXX7q3jZtJXK5c5qK83bSGMdV6tsOE70KbHoqJls4lE=
github.com/sendgrid/sendgrid-go v3.5.0+incompatible h1:kosbgHyNVYVaqECDYvFVLVD9nvThweBd6xp7vaCT3GI=
github.com/sendgrid/sendgrid-go v3.5.0+incompatible/go.mod h1:QRQt+LX/NmgVEvmdRw0VT/QgUn499+iza2FnDca9fg8=
github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ=
github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo=
github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0=
github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
@ -779,7 +812,7 @@ github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24 h1:pntxY8Ary0t4
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.1.1 h1:VzGj7lhU7KEB9e9gMpAV/v5XT2NVSvLJhJLCWbnkgXg=
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.1.1/go.mod h1:zrgwTnHtNr00buQ1vSptGe8m1f/BbgsPukg8qsT7A+A=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
@ -797,12 +830,12 @@ github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2
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/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.0-20181021141114-fe5e611709b0/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s=
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/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v0.0.0-20181024212040-082b515c9490/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
@ -811,25 +844,19 @@ github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DM
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271 h1:WhxRHzgeVGETMlmVfqhRn8RIeeNoPr2Czh33I4Zdccw=
github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0 h1:Hbg2NidpLE8veEBkEZTL3CvlkUIVzuU9jDplZO54c48=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
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/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tidwall/pretty v1.0.1 h1:WE4RBSZ1x6McVVC8S/Md+Qse8YUv6HRObAx6ke00NY8=
github.com/tidwall/pretty v1.0.1/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc h1:yUaosFVTJwnltaHbSNC3i82I92quFs+OFPRl8kNMVwo=
github.com/tmc/grpc-websocket-proxy v0.0.0-20200122045848-3419fae592fc/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
@ -857,12 +884,18 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY=
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI=
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b h1:vVRagRXf67ESqAb72hG2C/ZwI8NtJF2u2V76EsuOHGY=
github.com/yashtewari/glob-intersection v0.0.0-20180916065949-5c77d914dd0b/go.mod h1:HptNXiXVDcJjXe9SqMd0v2FsL9f8dz4GnXgltU6q/co=
github.com/yudai/gojsondiff v1.0.0 h1:27cbfqXLVEJ1o8I6v3y9lg8Ydm53EKqHXAOMxEGlCOA=
github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FBNExI05xg=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM=
github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI=
github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q=
@ -876,10 +909,11 @@ go.opencensus.io v0.15.0/go.mod h1:UffZAU+4sDEINUGP/B7UfBBkq4fqLu9zXAX7ke6CHW0=
go.opencensus.io v0.18.0/go.mod h1:vKdFvxhtzZ9onBp9VKHK8z/sRpBMnKAsufL7wlDrCOA=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2 h1:75k/FF0Q2YM8QYo07VPddOLBslDt1MZOdEslOHvmzAs=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4 h1:LYy1Hy3MJdrCdMwwzxA/dRok4ejH+RwNGbuoD9fCjto=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
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=
@ -887,10 +921,10 @@ go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/
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.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.11.0 h1:gSmpCfs+R47a4yQPAI4xJ0IPDLTRGXskm6UelqNXpqE=
go.uber.org/zap v1.11.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180820150726-614d502a4dac/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181001203147-e3636079e1a4/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181025213731-e84da0312774/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@ -907,41 +941,50 @@ golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550 h1:ObdrDkeb4kJdCP557AjRjq69pTHfNouLtWZG7j9rPN8=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191112222119-e1110fd1c708/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g=
golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a h1:aczoJ0HPNE92XKa7DrIzkNN6esOKO2TBwiiYoKcINhA=
golang.org/x/crypto v0.0.0-20200206161412-a0c6ece9d31a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 h1:vEg9joUBmeBcK9iSJftGNf3coIG4HqZElCPehJsfAYM=
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979 h1:Agxu5KLo8o7Bb634SVDnhIfpTvxmzUwhbYAzBvXt6h4=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299 h1:zQpM52jfKHG6II1ISZY1ZcpygvuSFZpLwfluuF89XOg=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181023182221-1baf3a9d7d67/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-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-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac h1:8R1esu+8QioDxo4E4mX6bFztO+dMTM49DNAaWfO5OeY=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/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/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
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=
@ -963,35 +1006,51 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL
golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/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-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190812203447-cdfb69ac37fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/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-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191112182307-2180aed22343/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-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344 h1:vGXIOMxbNfDTk/aXCmfdLgkrSV+Z2tcbze+pEc3v5W4=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202 h1:VvcQYSHwXgi7W+TpUR6A9g6Up98WAHf3f/ulnJ62IyA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/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-20190402181905-9f3314589c9a/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-20191202225959-858c2ad4c8b6/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/oauth2 v0.0.0-20200902213428-5d25da1a8d43 h1:ld7aEMNHoBnnDAX15v1T6z31v8HwR2A9FYOuAhWqkwc=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
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 h1:8gQV6CLnAEikrhgkHFbMAEhagSSnXWGV915qUMm9mrU=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180828065106-d99a578cf41b/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=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -1020,17 +1079,28 @@ golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47 h1:/XfQ9z7ib8eEJX2hdgFTZJ/ntt0swNk5oYBziWeTCvY=
golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1 h1:gZpLHxUX5BdYLA08Lj4YCJNN/jk7KtquiArPoeX0WvA=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5 h1:LfCXLvNmTYH9kEmVgqbnsWfruoXZIrh4YBgqVHtDvw0=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -1038,10 +1108,14 @@ golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20161028155119-f51c12702a4d/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 h1:SvFZT6jyqRaOeXpc5h/JSfZenJ2O330aBsf7JfSUXmQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=
@ -1065,17 +1139,44 @@ golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgw
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/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-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c h1:2EA2K0k9bcvvEDlqD8xdlOhCOqq+O/p9Voqi4x9W1YU=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858 h1:xLt+iB5ksWcZVxqc+g9K41ZHy+6MKWfXCDsjSThnsPA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7 h1:9zdDQZ7Thm29KFXgAX/+yaf3eVbP7djjWp/dXAppNCc=
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=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gonum.org/v1/gonum v0.0.0-20190331200053-3d26580ed485/go.mod h1:2ltnJ7xHfj0zHS40VVPYEAAMTa3ZGguvHGBSJeRWqE0=
gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw=
gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ=
@ -1085,15 +1186,29 @@ google.golang.org/api v0.6.0/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0 h1:yzlyyDW/J0w8yNFJIhiAJy4kq74S+1DOLdawELNxFMA=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.32.0 h1:Le77IccnTqEa8ryp9wIpX5W3zYm7Gf9LhOp9PHcwFts=
google.golang.org/api v0.32.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5 h1:tycE03LOZYQNhDpS27tcQdAzLCVMaj7QT2SXxebnpCM=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6 h1:lMO5rYAqUxkmaj76jAkRUvt5JZgFymx/+Q5Mzfivuhc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
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-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@ -1104,11 +1219,31 @@ google.golang.org/genproto v0.0.0-20190620144150-6af8c5fc6601/go.mod h1:z3L6/3dT
google.golang.org/genproto v0.0.0-20190716160619-c506a9f90610/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51 h1:Ex1mq5jaJof+kRnYi3SlYJ8KKa9Ao3NHyIT5XJ1gF6U=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150 h1:VPpdpQkGvFicX9yo4G5oxZPi9ALBnEOZblPSa/Wa2m4=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d h1:92D1fum1bJLKSdr11OJ+54YeCMCGYIygTA7R/YZxH5M=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
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=
@ -1117,34 +1252,55 @@ google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ij
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.32.0 h1:zWTV+LMdc3kaiJMSTOFz2UgSBgx8RNQoTGiZu3fR9S0=
google.golang.org/grpc v1.32.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.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/couchbase/gocb.v1 v1.6.4 h1:vAworfH5ZKDbonmayrwbGiD9jkAMroWmHXDf1GAIqMM=
gopkg.in/couchbase/gocb.v1 v1.6.4/go.mod h1:Ri5Qok4ZKiwmPr75YxZ0uELQy45XJgUSzeUnK806gTY=
gopkg.in/couchbase/gocbcore.v7 v7.1.15 h1:2nhfrqKz6TBex0Vcc+iq9UnAZltfCGklnM4mgdf2I3o=
gopkg.in/couchbase/gocbcore.v7 v7.1.15/go.mod h1:48d2Be0MxRtsyuvn+mWzqmoGUG9uA00ghopzOs148/E=
gopkg.in/couchbase/gocbcore.v7 v7.1.16 h1:n2foZ0Lg2ooBhgzgIpIxQ7VmlUbL6x0GwrP0GQcmTYo=
gopkg.in/couchbase/gocbcore.v7 v7.1.16/go.mod h1:48d2Be0MxRtsyuvn+mWzqmoGUG9uA00ghopzOs148/E=
gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4 h1:VVVoIV/nSw1w9ZnTEOjmkeJVcAzaCyxEujKglarxz7U=
gopkg.in/couchbaselabs/gocbconnstr.v1 v1.0.4/go.mod h1:ZjII0iKx4Veo6N6da+pEZu/ptNyKLg9QTVt7fFmR6sw=
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.3 h1:7n5tht07rEuogME6fmi6FVglIAUFD8m4t8ZQbnUq0sA=
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.3/go.mod h1:jl/gd/aQ2S8whKVSTnsPs6n7BPeaAuw9UglBD/OF7eo=
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4 h1:r5WoWGyeTJQiNGsoWAsMJfz0JFF14xc2TJrYSs09VXk=
gopkg.in/couchbaselabs/gojcbmock.v1 v1.0.4/go.mod h1:jl/gd/aQ2S8whKVSTnsPs6n7BPeaAuw9UglBD/OF7eo=
gopkg.in/couchbaselabs/jsonx.v1 v1.0.0 h1:SJGarb8dXAsVZWizC26rxBkBYEKhSUxVh5wAnyzBVaI=
gopkg.in/couchbaselabs/jsonx.v1 v1.0.0/go.mod h1:oR201IRovxvLW/eISevH12/+MiKHtNQAKfcX8iWZvJY=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fatih/pool.v2 v2.0.0 h1:xIFeWtxifuQJGk/IEPKsTduEKcKvPmhoiVDGpC40nKg=
gopkg.in/fatih/pool.v2 v2.0.0/go.mod h1:8xVGeu1/2jr2wm5V9SPuMht2H5AEmf5aFMGSQixtjTY=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/gorethink/gorethink.v4 v4.1.0 h1:xoE9qJ9Ae9KdKEsiQGCF44u2JdnjyohrMBRDtts3Gjw=
gopkg.in/gorethink/gorethink.v4 v4.1.0/go.mod h1:M7JgwrUAmshJ3iUbEK0Pt049MPyPK+CYDGGaEjdZb/c=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o=
gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
@ -1154,7 +1310,6 @@ gopkg.in/jcmturner/dnsutils.v1 v1.0.1 h1:cIuC1OLRGZrld+16ZJvvZxVJeKPsvd5eUIvxfoN
gopkg.in/jcmturner/dnsutils.v1 v1.0.1/go.mod h1:m3v+5svpVOhtFAP/wSz+yzh4Mc0Fg7eRhxkJMWSIz9Q=
gopkg.in/jcmturner/goidentity.v3 v3.0.0 h1:1duIyWiTaYvVx3YX2CYtpJbUFd7/UuPYCfgXtQ3VTbI=
gopkg.in/jcmturner/goidentity.v3 v3.0.0/go.mod h1:oG2kH0IvSYNIu80dVAyu/yoefjq1mNfM5bm88whjWx4=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3 h1:hHMV/yKPwMnJhPuPx7pH2Uw/3Qyf+thJYlisUc44010=
gopkg.in/jcmturner/gokrb5.v7 v7.2.3/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
gopkg.in/jcmturner/gokrb5.v7 v7.3.0 h1:0709Jtq/6QXEuWRfAm260XqlpcwL1vxtO1tUE2qK8Z4=
gopkg.in/jcmturner/gokrb5.v7 v7.3.0/go.mod h1:l8VISx+WGYp+Fp7KRbsiUuXTTOnxIc3Tuvyavf11/WM=
@ -1171,7 +1326,6 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkep
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
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.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
@ -1184,18 +1338,16 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh
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=
k8s.io/api v0.0.0-20190620084959-7cf5895f2711 h1:BblVYz/wE5WtBsD/Gvu54KyBUTJMflolzc5I2DTvh50=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/api v0.0.0-20190620084959-7cf5895f2711/go.mod h1:TBhBqb1AWbBQbW3XRusr7n7E4v2+5ZY8r8sAMnyFC5A=
k8s.io/api v0.17.0 h1:H9d/lw+VkZKEVIUc8F3wgiQ+FUXTTr21M87jXLU7yqM=
k8s.io/api v0.17.0/go.mod h1:npsyOePkeP0CPwyGfXDHxvypiYMJxBWAMpQxCaJ4ZxI=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719 h1:uV4S5IB5g4Nvi+TBVNf3e9L4wrirlwYJ6w88jUQxTUw=
k8s.io/apimachinery v0.0.0-20190612205821-1799e75a0719/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA=
k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d h1:7Kns6qqhMAQWvGkxYOLSLRZ5hJO0/5pcE5lPGP2fxUw=
k8s.io/apimachinery v0.0.0-20190817020851-f2f3a405f61d/go.mod h1:3jediapYqJ2w1BFw7lAZPCx7scubsTfosqHkhXCWJKw=
k8s.io/apimachinery v0.17.0 h1:xRBnuie9rXcPxUkDizUsGvPf1cnlZCFu210op7J7LJo=
k8s.io/apimachinery v0.17.0/go.mod h1:b9qmWdKlLuU9EBh+06BtLcSf/Mu89rWL33naRxs1uZg=
k8s.io/cli-runtime v0.17.0/go.mod h1:1E5iQpMODZq2lMWLUJELwRu2MLWIzwvMgDBpn3Y81Qo=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab h1:E8Fecph0qbNsAbijJJQryKu4Oi9QTp5cVpjTE+nqg6g=
k8s.io/client-go v0.0.0-20190620085101-78d2af792bab/go.mod h1:E95RaSlHr79aHaX0aGSwcPNfygDiPKOVXdmivCIZT0k=
k8s.io/client-go v0.17.0 h1:8QOGvUGdqDMFrm9sD6IUFl256BcffynGoe80sxgTEDg=
k8s.io/client-go v0.17.0/go.mod h1:TYgR6EUHs6k45hb6KWjVD6jFZvJV4gHDikv/It0xz+k=
@ -1204,16 +1356,13 @@ k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8
k8s.io/gengo v0.0.0-20190822140433-26a664648505/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0=
k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.0/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68=
k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk=
k8s.io/klog v0.4.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/klog v1.0.0 h1:Pt+yjF5aB1xDSVbau4VsWe+dQNzA0qv1LlXdC2dF6Q8=
k8s.io/klog v1.0.0/go.mod h1:4Bi6QPql/J/LkTDqv7R/cd3hPo4k2DG6Ptcz060Ez5I=
k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc=
k8s.io/kube-openapi v0.0.0-20190816220812-743ec37842bf/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a h1:UcxjrRMyNx/i/y8G7kPvLyy7rfbeuf1PYyBf973pgyU=
k8s.io/kube-openapi v0.0.0-20191107075043-30be4d16710a/go.mod h1:1TqjTSzOxsLGIKfj0lK8EeCP7K1iUG65v09OM0/WG5E=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da h1:ElyM7RPonbKnQqOcw7dG2IK5uvQQn3b/WPHqD5mBvP4=
k8s.io/utils v0.0.0-20190221042446-c2654d5206da/go.mod h1:8k8uAuAQ0rXslZKaEWd0c3oVhZz7sSzSiPnVZayjIX0=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f h1:GiPwtSzdP43eI1hpPCbROQCCIgCuiMMNF8YUVLF3vJo=
k8s.io/utils v0.0.0-20191114184206-e782cd3c129f/go.mod h1:sZAwmy6armz5eXlNoLmJcl4F1QuKu7sr+mFQ0byX7Ew=

View File

@ -6,9 +6,8 @@
package bearer
import (
"encoding/json"
"context"
"encoding/json"
"strings"
oidc "github.com/coreos/go-oidc"
@ -40,7 +39,6 @@ const (
// GetHandler retruns the HTTP handler provided by the middleware
func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error) {
meta, err := m.getNativeMetadata(metadata)
if err != nil {
return nil, err
}
@ -59,12 +57,14 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
authHeader := string(ctx.Request.Header.Peek(fasthttp.HeaderAuthorization))
if !strings.HasPrefix(strings.ToLower(authHeader), bearerPrefix) {
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
return
}
rawToken := authHeader[bearerPrefixLength:]
_, err := verifier.Verify(ctx, rawToken)
if err != nil {
ctx.Error(fasthttp.StatusMessage(fasthttp.StatusUnauthorized), fasthttp.StatusUnauthorized)
return
}
@ -84,5 +84,6 @@ func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*bearerMid
if err != nil {
return nil, err
}
return &middlewareMetadata, nil
}

View File

@ -22,6 +22,7 @@ func NewNetHTTPHandlerFunc(logger logger.Logger, h fasthttp.RequestHandler) http
reqBody, err := ioutil.ReadAll(r.Body)
if err != nil {
logger.Errorf("error reading request body, %+v", err)
return
}
c.Request.SetBody(reqBody)

View File

@ -1,6 +1,7 @@
package nethttpadaptor
import (
"context"
"io/ioutil"
"net/http"
"net/http/httptest"
@ -111,6 +112,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
"Body is handled",
func() *http.Request {
body := strings.NewReader("test body!")
return httptest.NewRequest("GET", "http://localhost:8080/test/sub", body)
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -137,6 +139,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
req.Header.Add("testHeaderKey1", "testHeaderValue1")
req.Header.Add("testHeaderKey2", "testHeaderValue2")
req.Header.Add("testHeaderKey3", "testHeaderValue3")
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -167,6 +170,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
req.Header.Add("testHeaderKey1", "testHeaderValue1")
req.Header.Add("testHeaderKey1", "testHeaderValue2")
req.Header.Add("testHeaderKey1", "testHeaderValue3")
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -208,6 +212,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
func() *http.Request {
req := httptest.NewRequest("GET", "https://localhost:8080", nil)
req.Header.Add("Content-Type", "application/json")
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -221,6 +226,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
func() *http.Request {
req := httptest.NewRequest("GET", "https://localhost:8080", nil)
req.Header.Add("Content-Type", "multipart/form-data; boundary=test-boundary")
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -234,6 +240,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
func() *http.Request {
req := httptest.NewRequest("GET", "https://localhost:8080", nil)
req.Header.Add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.103 Safari/537.36")
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -247,6 +254,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
func() *http.Request {
req := httptest.NewRequest("GET", "https://localhost:8080", nil)
req.Header.Add("Referer", "testReferer")
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -260,6 +268,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
func() *http.Request {
req := httptest.NewRequest("GET", "https://localhost:8080", nil)
req.Header.Add("Authorization", "Basic YWxhZGRpbjpvcGVuc2VzYW1l") // b64(aladdin:opensesame)
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -279,6 +288,7 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
func() *http.Request {
req := httptest.NewRequest("GET", "https://localhost:8080", nil)
req.RemoteAddr = "1.1.1.1"
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -290,7 +300,8 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
{
"nil body is handled",
func() *http.Request {
req, _ := http.NewRequest("GET", "https://localhost:8080", nil)
req, _ := http.NewRequestWithContext(context.Background(), "GET", "https://localhost:8080", nil)
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {
@ -302,7 +313,8 @@ func TestNewNetHTTPHandlerFuncRequests(t *testing.T) {
{
"proto headers are handled",
func() *http.Request {
req, _ := http.NewRequest("GET", "https://localhost:8080", nil)
req, _ := http.NewRequestWithContext(context.Background(), "GET", "https://localhost:8080", nil)
return req
},
func(t *testing.T) func(ctx *fasthttp.RequestCtx) {

View File

@ -6,11 +6,10 @@
package oauth2
import (
"context"
"encoding/json"
"strings"
"context"
"github.com/dapr/components-contrib/middleware"
"github.com/fasthttp-contrib/sessions"
"github.com/google/uuid"
@ -27,6 +26,7 @@ type oAuth2MiddlewareMetadata struct {
TokenURL string `json:"tokenURL"`
AuthHeaderName string `json:"authHeaderName"`
RedirectURL string `json:"redirectURL"`
ForceHTTPS string `json:"forceHTTPS"`
}
// NewOAuth2Middleware returns a new oAuth2 middleware
@ -43,6 +43,7 @@ const (
savedState = "auth-state"
redirectPath = "redirect-url"
codeParam = "code"
https = "https://"
)
// GetHandler retruns the HTTP handler provided by the middleware
@ -68,6 +69,7 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
if session.GetString(meta.AuthHeaderName) != "" {
ctx.Request.Header.Add(meta.AuthHeaderName, session.GetString(meta.AuthHeaderName))
h(ctx)
return
}
state := string(ctx.FormValue(stateParam))
@ -81,6 +83,9 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
} else {
authState := session.GetString(savedState)
redirectURL := session.GetString(redirectPath)
if strings.EqualFold(meta.ForceHTTPS, "true") {
redirectURL = https + string(ctx.Request.Host()) + redirectURL
}
if state != authState {
ctx.Error("invalid state", fasthttp.StatusBadRequest)
} else {
@ -113,5 +118,6 @@ func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*oAuth2Mid
if err != nil {
return nil, err
}
return &middlewareMetadata, nil
}

View File

@ -48,6 +48,7 @@ func NewOAuth2ClientCredentialsMiddleware(logger logger.Logger) *Middleware {
}
// Default: set Token Provider to real implementation (we will overwrite it for unit testing)
m.SetTokenProvider(m)
return m
}
@ -63,6 +64,7 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
meta, err := m.getNativeMetadata(metadata)
if err != nil {
m.log.Errorf("getNativeMetadata error, %s", err)
return nil, err
}
@ -70,13 +72,13 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
return func(ctx *fasthttp.RequestCtx) {
var headerValue string
// Check if valid Token is in the cache
var cacheKey = m.getCacheKey(meta)
cacheKey := m.getCacheKey(meta)
cachedToken, found := m.tokenCache.Get(cacheKey)
if !found {
m.log.Debugf("Cached token not found, try get one")
var endpointParams, err = url.ParseQuery(meta.EndpointParamsQuery)
endpointParams, err := url.ParseQuery(meta.EndpointParamsQuery)
if err != nil {
m.log.Errorf("Error parsing endpoint parameters, %s", err)
endpointParams, _ = url.ParseQuery("")
@ -94,6 +96,7 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
token, tokenError := m.tokenProvider.GetToken(conf)
if tokenError != nil {
m.log.Errorf("Error acquiring token, %s", tokenError)
return
}
@ -101,6 +104,7 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
m.log.Debugf("Duration in seconds %s, Expiry Time %s", tokenExpirationDuration, token.Expiry)
if err != nil {
m.log.Errorf("Error parsing duration string, %s", fmt.Sprintf("%ss", token.Expiry))
return
}
@ -167,6 +171,7 @@ func (m *Middleware) getCacheKey(meta *oAuth2ClientCredentialsMiddlewareMetadata
hashedKey := sha256.New()
key := strings.Join([]string{meta.ClientID, meta.Scopes}, "")
hashedKey.Write([]byte(key))
return fmt.Sprintf("%x", hashedKey.Sum(nil))
}
@ -178,5 +183,6 @@ func (m *Middleware) SetTokenProvider(tokenProvider TokenProviderInterface) {
// GetToken returns a token from the current OAuth2 ClientCredentials Configuration
func (m *Middleware) GetToken(conf *clientcredentials.Config) (*oauth2.Token, error) {
tokenSource := conf.TokenSource(context.Background())
return tokenSource.Token()
}

View File

@ -9,10 +9,9 @@ import (
"testing"
"time"
"github.com/dapr/components-contrib/middleware"
mock "github.com/dapr/components-contrib/middleware/http/oauth2clientcredentials/mocks"
"github.com/dapr/dapr/pkg/logger"
"github.com/dapr/components-contrib/middleware"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

View File

@ -0,0 +1,202 @@
package opa
import (
"bytes"
"context"
"encoding/json"
"errors"
"strings"
"github.com/dapr/components-contrib/middleware"
"github.com/dapr/dapr/pkg/logger"
"github.com/open-policy-agent/opa/rego"
"github.com/valyala/fasthttp"
)
type middlewareMetadata struct {
Rego string `json:"rego"`
DefaultStatus int `json:"defaultStatus,omitempty"`
IncludedHeaders string `json:"includedHeaders,omitempty"`
}
// NewMiddleware returns a new Open Policy Agent middleware
func NewMiddleware(logger logger.Logger) *Middleware {
return &Middleware{logger: logger}
}
// Middleware is an OPA middleware
type Middleware struct {
logger logger.Logger
}
// RegoResult is the expected result from rego policy
type RegoResult struct {
Allow bool `json:"allow"`
AdditionalHeaders map[string]string `json:"additional_headers,omitempty"`
StatusCode int `json:"status_code,omitempty"`
}
const opaErrorHeaderKey = "x-dapr-opa-error"
var (
errOpaNoResult = errors.New("received no results back from rego policy. Are you setting data.http.allow?")
errOpaInvalidResultType = errors.New("got an invalid type back from repo policy. Only a boolean or map is valid")
)
// GetHandler returns the HTTP handler provided by the middleware
func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.RequestHandler) fasthttp.RequestHandler, error) {
meta, err := m.getNativeMetadata(metadata)
if err != nil {
return nil, err
}
ctx := context.Background()
query, err := rego.New(
rego.Query("result = data.http.allow"),
rego.Module("inline.rego", meta.Rego),
).PrepareForEval(ctx)
if err != nil {
return nil, err
}
return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
return func(ctx *fasthttp.RequestCtx) {
if handled := m.evalRequest(ctx, meta, &query); handled {
return
}
h(ctx)
}
}, nil
}
func (m *Middleware) evalRequest(ctx *fasthttp.RequestCtx, meta *middlewareMetadata, query *rego.PreparedEvalQuery) bool {
headers := map[string]string{}
allowedHeaders := strings.Split(meta.IncludedHeaders, ",")
ctx.Request.Header.VisitAll(func(key, value []byte) {
for _, allowedHeader := range allowedHeaders {
buf := []byte("")
result := fasthttp.AppendNormalizedHeaderKeyBytes(buf, []byte(allowedHeader))
normalizedHeader := result[0:]
if bytes.Equal(key, normalizedHeader) {
headers[string(key)] = string(value)
}
}
})
queryArgs := map[string][]string{}
ctx.QueryArgs().VisitAll(func(key, value []byte) {
if val, ok := queryArgs[string(key)]; ok {
queryArgs[string(key)] = append(val, string(value))
} else {
queryArgs[string(key)] = []string{string(value)}
}
})
path := string(ctx.Path())
pathParts := strings.Split(strings.Trim(path, "/"), "/")
input := map[string]interface{}{
"request": map[string]interface{}{
"method": string(ctx.Method()),
"path": path,
"path_parts": pathParts,
"raw_query": string(ctx.QueryArgs().QueryString()),
"query": queryArgs,
"headers": headers,
"scheme": string(ctx.Request.URI().Scheme()),
},
}
results, err := query.Eval(context.TODO(), rego.EvalInput(input))
if err != nil {
m.opaError(ctx, meta, err)
return false
}
if len(results) == 0 {
m.opaError(ctx, meta, errOpaNoResult)
return false
}
return m.handleRegoResult(ctx, meta, results[0].Bindings["result"])
}
// handleRegoResult takes the in process request and open policy agent evaluation result
// and maps it the appropriate response or headers.
// It returns true if the request should continue, or false if a response should be immediately returned.
func (m *Middleware) handleRegoResult(ctx *fasthttp.RequestCtx, meta *middlewareMetadata, result interface{}) bool {
if allowed, ok := result.(bool); ok {
if !allowed {
ctx.Error(fasthttp.StatusMessage(meta.DefaultStatus), meta.DefaultStatus)
}
return allowed
}
if _, ok := result.(map[string]interface{}); !ok {
m.opaError(ctx, meta, errOpaInvalidResultType)
return false
}
// Is it expensive to marshal back and forth? Should we just manually pull out properties?
marshaled, err := json.Marshal(result)
if err != nil {
m.opaError(ctx, meta, err)
return false
}
regoResult := RegoResult{
// By default, a non-allowed request with return a 403 response.
StatusCode: meta.DefaultStatus,
AdditionalHeaders: make(map[string]string),
}
if err = json.Unmarshal(marshaled, &regoResult); err != nil {
m.opaError(ctx, meta, err)
return false
}
// If the result isn't allowed, set the response status and
// apply the additional headers to the response.
// Otherwise, set the headers on the ongoing request (overriding as necessary)
if !regoResult.Allow {
ctx.Error(fasthttp.StatusMessage(regoResult.StatusCode), regoResult.StatusCode)
for key, value := range regoResult.AdditionalHeaders {
ctx.Response.Header.Set(key, value)
}
} else {
for key, value := range regoResult.AdditionalHeaders {
ctx.Request.Header.Set(key, value)
}
}
return regoResult.Allow
}
func (m *Middleware) opaError(ctx *fasthttp.RequestCtx, meta *middlewareMetadata, err error) {
ctx.Error(fasthttp.StatusMessage(meta.DefaultStatus), meta.DefaultStatus)
ctx.Response.Header.Set(opaErrorHeaderKey, "true")
m.logger.Warnf("Error procesing rego policy: %v", err)
}
func (m *Middleware) getNativeMetadata(metadata middleware.Metadata) (*middlewareMetadata, error) {
b, err := json.Marshal(metadata.Properties)
if err != nil {
return nil, err
}
meta := middlewareMetadata{
DefaultStatus: 403,
}
err = json.Unmarshal(b, &meta)
if err != nil {
return nil, err
}
return &meta, nil
}

View File

@ -0,0 +1,234 @@
package opa
import (
"testing"
"github.com/dapr/components-contrib/middleware"
"github.com/dapr/dapr/pkg/logger"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
fh "github.com/valyala/fasthttp"
)
func mockedRequestHandler(ctx *fh.RequestCtx) {}
type RequestConfiguator func(*fh.RequestCtx)
func TestOpaPolicy(t *testing.T) {
tests := map[string]struct {
meta middleware.Metadata
req RequestConfiguator
status int
headers *[][]string
shouldHandlerError bool
shouldRegoError bool
}{
"allow": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
allow = true`,
},
},
status: 200,
},
"deny": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
allow = false`,
},
},
status: 403,
},
"status": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
allow = {
"allow": false,
"status_code": 301
}`,
},
},
status: 301,
},
"add redirect": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
allow = {
"allow": false,
"status_code": 301,
"additional_headers": { "location": "https://my.site/login" }
}`,
},
},
status: 301,
headers: &[][]string{
{"location", "https://my.site/login"},
},
},
"add headers": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
allow = {
"allow": false,
"status_code": 301,
"additional_headers": { "x-key": "abc" }
}`,
},
},
status: 301,
headers: &[][]string{
{"x-key", "abc"},
},
},
"allow with path": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
default allow = true
allow = { "status_code": 403 } {
input.request.path_parts[0] = "forbidden"
}
`,
},
},
req: func(ctx *fh.RequestCtx) {
ctx.Request.SetHost("https://my.site")
ctx.Request.URI().SetPath("/allowed")
},
status: 200,
},
"deny with path": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
default allow = true
allow = { "status_code": 403 } {
input.request.path_parts[0] = "forbidden"
}
`,
},
},
req: func(ctx *fh.RequestCtx) {
ctx.Request.SetHost("https://my.site")
ctx.Request.URI().SetPath("/forbidden")
},
status: 403,
},
"allow when header not included": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
default allow = true
allow = { "status_code": 403 } {
input.request.headers["x-bad-header"] = "1"
}
`,
},
},
req: func(ctx *fh.RequestCtx) {
ctx.Request.SetHost("https://my.site")
ctx.Request.Header.Add("x-bad-header", "1")
},
status: 200,
},
"deny when header included": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
default allow = true
allow = { "status_code": 403 } {
input.request.headers["X-Bad-Header"] = "1"
}
`,
"includedHeaders": "x-bad-header",
},
},
req: func(ctx *fh.RequestCtx) {
ctx.Request.SetHost("https://my.site")
ctx.Request.Header.Add("x-bad-header", "1")
},
status: 403,
},
"err on no rego": {
meta: middleware.Metadata{
Properties: map[string]string{},
},
shouldHandlerError: true,
},
"err on bad allow": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http
allow = 1`,
},
},
shouldRegoError: true,
},
"err on bad package": {
meta: middleware.Metadata{
Properties: map[string]string{
"rego": `
package http.authz
allow = true`,
},
},
shouldRegoError: true,
},
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
log := logger.NewLogger("opa.test")
opaMiddleware := NewMiddleware(log)
handler, err := opaMiddleware.GetHandler(test.meta)
if test.shouldHandlerError {
require.Error(t, err)
return
}
require.NoError(t, err)
var reqCtx fh.RequestCtx
if test.req != nil {
test.req(&reqCtx)
}
handler(mockedRequestHandler)(&reqCtx)
if test.shouldRegoError {
assert.Equal(t, 403, reqCtx.Response.StatusCode())
assert.Equal(t, "true", string(reqCtx.Response.Header.Peek(opaErrorHeaderKey)))
return
}
assert.Equal(t, test.status, reqCtx.Response.StatusCode())
if test.headers != nil {
for _, header := range *test.headers {
assert.Equal(t, header[1], string(reqCtx.Response.Header.Peek(header[0])))
}
}
})
}
}

View File

@ -51,6 +51,7 @@ func (m *Middleware) GetHandler(metadata middleware.Metadata) (func(h fasthttp.R
return func(h fasthttp.RequestHandler) fasthttp.RequestHandler {
limitHandler := tollbooth.LimitFuncHandler(limiter, nethttpadaptor.NewNetHTTPHandlerFunc(m.logger, h))
wrappedHandler := fasthttpadaptor.NewFastHTTPHandlerFunc(limitHandler.ServeHTTP)
return func(ctx *fasthttp.RequestCtx) {
wrappedHandler(ctx)
}

View File

@ -37,7 +37,7 @@ func (m *resolver) Init(metadata nameresolution.Metadata) error {
var hostAddress string
var ok bool
var props = metadata.Properties
props := metadata.Properties
if id, ok = props[nameresolution.MDNSInstanceName]; !ok {
return errors.New("name is missing")
@ -83,6 +83,7 @@ func (m *resolver) registerMDNS(id string, ips []string, port int) error {
if err != nil {
started <- false
m.logger.Errorf("error from zeroconf register: %s", err)
return
}
started <- true
@ -96,6 +97,7 @@ func (m *resolver) registerMDNS(id string, ips []string, port int) error {
}()
<-started
return err
}
@ -127,6 +129,7 @@ func (m *resolver) ResolveID(req nameresolution.ResolveRequest) (string, error)
// cancel timeout because it found the service
cancel()
return
}
}
@ -136,6 +139,7 @@ func (m *resolver) ResolveID(req nameresolution.ResolveRequest) (string, error)
if err = resolver.Browse(ctx, req.ID, "local.", entries); err != nil {
// cancel context
cancel()
return "", fmt.Errorf("failed to browse: %s", err.Error())
}

View File

@ -15,7 +15,7 @@ import (
)
func TestInit(t *testing.T) {
var tests = []struct {
tests := []struct {
missingProp string
props map[string]string
}{

View File

@ -9,14 +9,12 @@ import (
"strconv"
"strings"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/aws/aws-sdk-go/aws"
"github.com/dapr/dapr/pkg/logger"
sns "github.com/aws/aws-sdk-go/service/sns"
sqs "github.com/aws/aws-sdk-go/service/sqs"
aws_auth "github.com/dapr/components-contrib/authentication/aws"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
)
type snsSqs struct {
@ -25,12 +23,13 @@ type snsSqs struct {
// key is the hashed topic name, value is the actual topic name
topicHash map[string]string
// key is the topic name, value holds the ARN of the queue and its url
queues map[string]*sqsQueueInfo
awsAcctID string
snsClient *sns.SNS
sqsClient *sqs.SQS
metadata *snsSqsMetadata
logger logger.Logger
queues map[string]*sqsQueueInfo
awsAcctID string
snsClient *sns.SNS
sqsClient *sqs.SQS
metadata *snsSqsMetadata
logger logger.Logger
subscriptions []*string
}
type sqsQueueInfo struct {
@ -69,15 +68,15 @@ const (
)
func NewSnsSqs(l logger.Logger) pubsub.PubSub {
return &snsSqs{logger: l}
return &snsSqs{logger: l, subscriptions: []*string{}}
}
func parseInt64(input string, propertyName string) (int64, error) {
number, err := strconv.Atoi(input)
if err != nil {
return -1, fmt.Errorf("parsing %s failed with: %v", propertyName, err)
}
return int64(number), nil
}
@ -86,6 +85,7 @@ func parseInt64(input string, propertyName string) (int64, error) {
func nameToHash(name string) string {
h := sha256.New()
h.Write([]byte(name))
return fmt.Sprintf("%x", h.Sum(nil))
}
@ -132,7 +132,6 @@ func (s *snsSqs) getSnsSqsMetatdata(metadata pubsub.Metadata) (*snsSqsMetadata,
md.messageVisibilityTimeout = 10
} else {
timeout, err := parseInt64(val, "messageVisibilityTimeout")
if err != nil {
return nil, err
}
@ -148,7 +147,6 @@ func (s *snsSqs) getSnsSqsMetatdata(metadata pubsub.Metadata) (*snsSqsMetadata,
md.messageRetryLimit = 10
} else {
retryLimit, err := parseInt64(val, "messageRetryLimit")
if err != nil {
return nil, err
}
@ -164,7 +162,6 @@ func (s *snsSqs) getSnsSqsMetatdata(metadata pubsub.Metadata) (*snsSqsMetadata,
md.messageWaitTimeSeconds = 1
} else {
waitTime, err := parseInt64(val, "messageWaitTimeSeconds")
if err != nil {
return nil, err
}
@ -180,7 +177,6 @@ func (s *snsSqs) getSnsSqsMetatdata(metadata pubsub.Metadata) (*snsSqsMetadata,
md.messageMaxNumber = 10
} else {
maxNumber, err := parseInt64(val, "messageMaxNumber")
if err != nil {
return nil, err
}
@ -199,7 +195,6 @@ func (s *snsSqs) getSnsSqsMetatdata(metadata pubsub.Metadata) (*snsSqsMetadata,
func (s *snsSqs) Init(metadata pubsub.Metadata) error {
md, err := s.getSnsSqsMetatdata(metadata)
if err != nil {
return err
}
@ -218,6 +213,7 @@ func (s *snsSqs) Init(metadata pubsub.Metadata) error {
}
s.snsClient = sns.New(sess)
s.sqsClient = sqs.New(sess)
return nil
}
@ -227,7 +223,6 @@ func (s *snsSqs) createTopic(topic string) (string, string, error) {
Name: aws.String(hashedName),
Tags: []*sns.Tag{{Key: aws.String(awsSnsTopicNameKey), Value: aws.String(topic)}},
})
if err != nil {
return "", "", err
}
@ -242,15 +237,16 @@ func (s *snsSqs) getOrCreateTopic(topic string) (string, error) {
if ok {
s.logger.Debugf("Found existing topic ARN for topic %s: %s", topic, topicArn)
return topicArn, nil
}
s.logger.Debugf("No topic ARN found for %s\n Creating topic instead.", topic)
topicArn, hashedName, err := s.createTopic(topic)
if err != nil {
s.logger.Errorf("error creating new topic %s: %v", topic, err)
return "", err
}
@ -266,7 +262,6 @@ func (s *snsSqs) createQueue(queueName string) (*sqsQueueInfo, error) {
QueueName: aws.String(nameToHash(queueName)),
Tags: map[string]*string{awsSqsQueueNameKey: aws.String(queueName)},
})
if err != nil {
return nil, err
}
@ -275,7 +270,6 @@ func (s *snsSqs) createQueue(queueName string) (*sqsQueueInfo, error) {
AttributeNames: []*string{aws.String("QueueArn")},
QueueUrl: createQueueResponse.QueueUrl,
})
if err != nil {
s.logger.Errorf("error fetching queue attributes for %s: %v", queueName, err)
}
@ -310,15 +304,16 @@ func (s *snsSqs) getOrCreateQueue(queueName string) (*sqsQueueInfo, error) {
if ok {
s.logger.Debugf("Found queue arn for %s: %s", queueName, queueArn)
return queueArn, nil
}
// creating queues is idempotent, the names serve as unique keys among a given region
s.logger.Debugf("No queue arn found for %s\nCreating queue", queueName)
queueInfo, err := s.createQueue(queueName)
if err != nil {
s.logger.Errorf("Error creating queue %s: %v", queueName, err)
return nil, err
}
@ -329,7 +324,6 @@ func (s *snsSqs) getOrCreateQueue(queueName string) (*sqsQueueInfo, error) {
func (s *snsSqs) Publish(req *pubsub.PublishRequest) error {
topicArn, err := s.getOrCreateTopic(req.Topic)
if err != nil {
s.logger.Errorf("error getting topic ARN for %s: %v", req.Topic, err)
}
@ -342,6 +336,7 @@ func (s *snsSqs) Publish(req *pubsub.PublishRequest) error {
if err != nil {
s.logger.Errorf("error publishing topic %s with topic ARN %s: %v", req.Topic, topicArn, err)
return err
}
@ -376,7 +371,6 @@ func (s *snsSqs) handleMessage(message *sqs.Message, queueInfo *sqsQueueInfo, ha
}
recvCountInt, err := strconv.ParseInt(*recvCount, 10, 32)
if err != nil {
return fmt.Errorf("error parsing ApproximateReceiveCount from message: %v", message)
}
@ -428,15 +422,16 @@ func (s *snsSqs) consumeSubscription(queueInfo *sqsQueueInfo, handler pubsub.Han
VisibilityTimeout: aws.Int64(s.metadata.messageVisibilityTimeout),
WaitTimeSeconds: aws.Int64(s.metadata.messageWaitTimeSeconds),
})
if err != nil {
s.logger.Errorf("error consuming topic: %v", err)
continue
}
// retry receiving messages
if len(messageResponse.Messages) < 1 {
s.logger.Debug("No messages received, requesting again")
continue
}
@ -457,17 +452,17 @@ func (s *snsSqs) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler)
// these should be idempotent
// queues should not be created if they exist
topicArn, err := s.getOrCreateTopic(req.Topic)
if err != nil {
s.logger.Errorf("error getting topic ARN for %s: %v", req.Topic, err)
return err
}
// this is the ID of the application, it is supplied via runtime as "consumerID"
queueInfo, err := s.getOrCreateQueue(s.metadata.sqsQueueName)
if err != nil {
s.logger.Errorf("error retrieving SQS queue: %v", err)
return err
}
@ -479,15 +474,26 @@ func (s *snsSqs) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler)
ReturnSubscriptionArn: nil,
TopicArn: &topicArn,
})
if err != nil {
s.logger.Errorf("error subscribing to topic %s: %v", req.Topic, err)
return err
}
s.subscriptions = append(s.subscriptions, subscribeOutput.SubscriptionArn)
s.logger.Debugf("Subscribed to topic %s: %v", req.Topic, subscribeOutput)
s.consumeSubscription(queueInfo, handler)
return nil
}
func (s *snsSqs) Close() error {
for _, sub := range s.subscriptions {
s.snsClient.Unsubscribe(&sns.UnsubscribeInput{
SubscriptionArn: sub,
})
}
return nil
}

View File

@ -103,12 +103,12 @@ func (aeh *AzureEventHubs) Init(metadata pubsub.Metadata) error {
}
aeh.metadata = m
hub, err := eventhub.NewHubFromConnectionString(aeh.metadata.connectionString)
if err != nil {
return fmt.Errorf("unable to connect to azure event hubs: %v", err)
}
aeh.hub = hub
return nil
}
@ -118,6 +118,7 @@ func (aeh *AzureEventHubs) Publish(req *pubsub.PublishRequest) error {
if err != nil {
return fmt.Errorf("error from publish: %s", err)
}
return nil
}
@ -134,7 +135,6 @@ func (aeh *AzureEventHubs) Subscribe(req pubsub.SubscribeRequest, handler pubsub
}
processor, err := eph.NewFromConnectionString(context.Background(), aeh.metadata.connectionString, leaserCheckpointer, leaserCheckpointer, eph.WithNoBanner(), eph.WithConsumerGroup(aeh.metadata.consumerGroup))
if err != nil {
return err
}
@ -154,3 +154,7 @@ func (aeh *AzureEventHubs) Subscribe(req pubsub.SubscribeRequest, handler pubsub
return nil
}
func (aeh *AzureEventHubs) Close() error {
return aeh.hub.Close(context.TODO())
}

View File

@ -57,7 +57,8 @@ func TestParseEventHubsMetadata(t *testing.T) {
"missing storageContainerName",
map[string]string{"consumerID": "fake", "connectionString": "fake", "storageAccountName": "name", "storageAccountKey": "key"},
missingStorageContainerNameErrorMsg,
}}
},
}
for _, c := range invalidConfigTestCases {
t.Run(c.name, func(t *testing.T) {

View File

@ -51,16 +51,18 @@ const (
type handle = struct{}
type azureServiceBus struct {
metadata metadata
namespace *azservicebus.Namespace
topicManager *azservicebus.TopicManager
logger logger.Logger
metadata metadata
namespace *azservicebus.Namespace
topicManager *azservicebus.TopicManager
logger logger.Logger
subscriptions []*subscription
}
// NewAzureServiceBus returns a new Azure ServiceBus pub-sub implementation
func NewAzureServiceBus(logger logger.Logger) pubsub.PubSub {
return &azureServiceBus{
logger: logger,
logger: logger,
subscriptions: []*subscription{},
}
}
@ -202,6 +204,7 @@ func (a *azureServiceBus) Init(metadata pubsub.Metadata) error {
}
a.topicManager = a.namespace.NewTopicManager()
return nil
}
@ -226,6 +229,7 @@ func (a *azureServiceBus) Publish(req *pubsub.PublishRequest) error {
if err != nil {
return err
}
return nil
}
@ -258,6 +262,7 @@ func (a *azureServiceBus) Subscribe(req pubsub.SubscribeRequest, handler pubsub.
select {
case <-reconnCtx.Done():
a.logger.Debugf("Reconnect context for topic %s is done", req.Topic)
return
case <-time.After(2 * time.Minute):
attempts := readAttemptsStale()
@ -274,6 +279,7 @@ func (a *azureServiceBus) Subscribe(req pubsub.SubscribeRequest, handler pubsub.
topic, err := a.namespace.NewTopic(req.Topic)
if err != nil {
a.logger.Errorf("%s could not instantiate topic %s, %s", errorMessagePrefix, req.Topic, err)
return
}
@ -284,10 +290,11 @@ func (a *azureServiceBus) Subscribe(req pubsub.SubscribeRequest, handler pubsub.
subEntity, err := topic.NewSubscription(subID, opts...)
if err != nil {
a.logger.Errorf("%s could not instantiate subscription %s for topic %s", errorMessagePrefix, subID, req.Topic)
return
}
sub := newSubscription(req.Topic, subEntity, a.metadata.MaxConcurrentHandlers, a.logger)
a.subscriptions = append(a.subscriptions, sub)
// ReceiveAndBlock will only return with an error
// that it cannot handle internally. The subscription
// connection is closed when this method returns.
@ -310,6 +317,7 @@ func (a *azureServiceBus) Subscribe(req pubsub.SubscribeRequest, handler pubsub.
attempts := readAttemptsStale()
if attempts == 0 {
a.logger.Errorf("Subscription to topic %s lost connection, unable to recover after %d attempts", sub.topic, maxReconnAttempts)
return
}
@ -334,6 +342,7 @@ func (a *azureServiceBus) ensureTopic(topic string) error {
return err
}
}
return nil
}
@ -359,6 +368,7 @@ func (a *azureServiceBus) ensureSubscription(name string, topic string) error {
return err
}
}
return nil
}
@ -373,6 +383,7 @@ func (a *azureServiceBus) getTopicEntity(topic string) (*azservicebus.TopicEntit
if err != nil && !azservicebus.IsErrNotFound(err) {
return nil, fmt.Errorf("%s could not get topic %s, %s", errorMessagePrefix, topic, err)
}
return topicEntity, nil
}
@ -383,6 +394,7 @@ func (a *azureServiceBus) createTopicEntity(topic string) error {
if err != nil {
return fmt.Errorf("%s could not put topic %s, %s", errorMessagePrefix, topic, err)
}
return nil
}
@ -393,6 +405,7 @@ func (a *azureServiceBus) getSubscriptionEntity(mgr *azservicebus.SubscriptionMa
if err != nil && !azservicebus.IsErrNotFound(err) {
return nil, fmt.Errorf("%s could not get subscription %s, %s", errorMessagePrefix, subscription, err)
}
return entity, nil
}
@ -409,6 +422,7 @@ func (a *azureServiceBus) createSubscriptionEntity(mgr *azservicebus.Subscriptio
if err != nil {
return fmt.Errorf("%s could not put subscription %s, %s", errorMessagePrefix, subscription, err)
}
return nil
}
@ -426,13 +440,23 @@ func (a *azureServiceBus) createSubscriptionManagementOptions() ([]azservicebus.
if a.metadata.AutoDeleteOnIdleInSec != nil {
opts = append(opts, subscriptionManagementOptionsWithAutoDeleteOnIdle(a.metadata.AutoDeleteOnIdleInSec))
}
return opts, nil
}
func (a *azureServiceBus) Close() error {
for _, s := range a.subscriptions {
s.close(context.TODO())
}
return nil
}
func subscriptionManagementOptionsWithMaxDeliveryCount(maxDeliveryCount *int) azservicebus.SubscriptionManagementOption {
return func(d *azservicebus.SubscriptionDescription) error {
mdc := int32(*maxDeliveryCount)
d.MaxDeliveryCount = &mdc
return nil
}
}
@ -441,6 +465,7 @@ func subscriptionManagementOptionsWithAutoDeleteOnIdle(durationInSec *int) azser
return func(d *azservicebus.SubscriptionDescription) error {
duration := fmt.Sprintf("PT%dS", *durationInSec)
d.AutoDeleteOnIdle = &duration
return nil
}
}
@ -449,6 +474,7 @@ func subscriptionManagementOptionsWithDefaultMessageTimeToLive(durationInSec *in
return func(d *azservicebus.SubscriptionDescription) error {
duration := fmt.Sprintf("PT%dS", *durationInSec)
d.DefaultMessageTimeToLive = &duration
return nil
}
}
@ -457,6 +483,7 @@ func subscriptionManagementOptionsWithLockDuration(durationInSec *int) azservice
return func(d *azservicebus.SubscriptionDescription) error {
duration := fmt.Sprintf("PT%dS", *durationInSec)
d.LockDuration = &duration
return nil
}
}

View File

@ -8,9 +8,8 @@ package servicebus
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/dapr/components-contrib/pubsub"
"github.com/stretchr/testify/assert"
)
const (

View File

@ -55,12 +55,14 @@ func (s *subscription) ReceiveAndBlock(ctx context.Context, handler pubsub.Handl
shouldRenewLocks := lockRenewalInSec > 0
if !shouldRenewLocks {
s.logger.Debugf("Lock renewal for topic %s disabled", s.topic)
return
}
for {
select {
case <-ctx.Done():
s.logger.Debugf("Lock renewal context for topic %s done", s.topic)
return
case <-time.After(time.Second * time.Duration(lockRenewalInSec)):
s.tryRenewLocks()
@ -82,6 +84,7 @@ func (s *subscription) ReceiveAndBlock(ctx context.Context, handler pubsub.Handl
select {
case <-ctx.Done():
s.logger.Debugf("Receive context for topic %s done", s.topic)
return ctx.Err()
case <-time.After(time.Second * time.Duration(maxActiveMessagesRecoveryInSec)):
continue
@ -151,6 +154,7 @@ func (s *subscription) asyncWrapper(handlerFunc azservicebus.HandlerFunc) azserv
select {
case <-ctx.Done():
s.logger.Debugf("Message context done for %s on topic %s", msg.ID, s.topic)
return
case <-s.handleChan: // Take or wait on a free handle before getting a new message
s.logger.Debugf("Taken message handle for %s on topic %s", msg.ID, s.topic)
@ -168,6 +172,7 @@ func (s *subscription) asyncWrapper(handlerFunc azservicebus.HandlerFunc) azserv
s.logger.Errorf("%s error handling message %s on topic '%s', %s", errorMessagePrefix, msg.ID, s.topic, err)
}
}()
return nil
}
}
@ -178,6 +183,7 @@ func (s *subscription) tryRenewLocks() {
s.mu.RUnlock()
if activeMessageLen == 0 {
s.logger.Debugf("No active messages require lock renewal for topic %s", s.topic)
return
}
@ -202,16 +208,19 @@ func (s *subscription) receiveMessage(ctx context.Context, handler azservicebus.
if err := s.entity.ReceiveOne(ctx, handler); err != nil {
return fmt.Errorf("%s error receiving message on topic %s, %s", errorMessagePrefix, s.topic, err)
}
return nil
}
func (s *subscription) abandonMessage(ctx context.Context, m *azservicebus.Message) error {
s.logger.Debugf("Abandoning message %s on topic %s", m.ID, s.topic)
return m.Abandon(ctx)
}
func (s *subscription) completeMessage(ctx context.Context, m *azservicebus.Message) error {
s.logger.Debugf("Completing message %s on topic %s", m.ID, s.topic)
return m.Complete(ctx)
}

View File

@ -15,7 +15,7 @@ const (
DefaultCloudEventType = "com.dapr.event.sent"
// CloudEventsSpecVersion is the specversion used by Dapr for the cloud events implementation
CloudEventsSpecVersion = "1.0"
//ContentType is the Cloud Events HTTP content type
// ContentType is the Cloud Events HTTP content type
ContentType = "application/cloudevents+json"
// DefaultCloudEventSource is the default event source
DefaultCloudEventSource = "Dapr"
@ -111,5 +111,6 @@ func getStrVal(m map[string]interface{}, key string) string {
return s
}
}
return ""
}

View File

@ -55,15 +55,17 @@ func (g *GCPPubSub) Init(meta pubsub.Metadata) error {
g.client = pubsubClient
g.metadata = &pubsubMeta
return nil
}
func (g *GCPPubSub) parseMetadata(metadata pubsub.Metadata) ([]byte, error) {
b, err := json.Marshal(metadata.Properties)
return b, err
}
//Publish the topic to GCP Pubsub
// Publish the topic to GCP Pubsub
func (g *GCPPubSub) Publish(req *pubsub.PublishRequest) error {
if !g.metadata.DisableEntityManagement {
err := g.ensureTopic(req.Topic)
@ -116,6 +118,7 @@ func (g *GCPPubSub) handleSubscriptionMessages(topic *gcppubsub.Topic, sub *gcpp
m.Ack()
}
})
return err
}
@ -125,6 +128,7 @@ func (g *GCPPubSub) ensureTopic(topic string) error {
if !exists {
_, err = g.client.CreateTopic(context.Background(), topic)
}
return err
}
@ -144,9 +148,14 @@ func (g *GCPPubSub) ensureSubscription(subscription string, topic string) error
_, subErr = g.client.CreateSubscription(context.Background(), g.metadata.ConsumerID,
gcppubsub.SubscriptionConfig{Topic: g.getTopic(topic)})
}
return subErr
}
func (g *GCPPubSub) getSubscription(subscription string) *gcppubsub.Subscription {
return g.client.Subscription(subscription)
}
func (g *GCPPubSub) Close() error {
return g.client.Close()
}

View File

@ -4,16 +4,17 @@ import (
"encoding/json"
"testing"
"github.com/stretchr/testify/assert"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
"github.com/stretchr/testify/assert"
)
func TestInit(t *testing.T) {
m := pubsub.Metadata{}
m.Properties = map[string]string{"auth_provider_x509_cert_url": "https://auth", "auth_uri": "https://auth", "client_x509_cert_url": "https://cert", "client_email": "test@test.com", "client_id": "id", "private_key": "****",
"private_key_id": "key_id", "project_id": "project1", "token_uri": "https://token", "type": "serviceaccount"}
m.Properties = map[string]string{
"auth_provider_x509_cert_url": "https://auth", "auth_uri": "https://auth", "client_x509_cert_url": "https://cert", "client_email": "test@test.com", "client_id": "id", "private_key": "****",
"private_key_id": "key_id", "project_id": "project1", "token_uri": "https://token", "type": "serviceaccount",
}
ps := GCPPubSub{logger: logger.NewLogger("test")}
b, err := ps.parseMetadata(m)
assert.Nil(t, err)

View File

@ -33,6 +33,7 @@ func parseHazelcastMetadata(meta pubsub.Metadata) (metadata, error) {
} else {
return m, errors.New("hazelcast error: missing hazelcast servers")
}
return m, nil
}
@ -82,6 +83,12 @@ func (p *Hazelcast) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handle
return nil
}
func (p *Hazelcast) Close() error {
p.client.Shutdown()
return nil
}
type hazelcastMessageListener struct {
topicName string
pubsubHandler pubsub.Handler
@ -92,6 +99,7 @@ func (l *hazelcastMessageListener) OnMessage(message hazelcastCore.Message) erro
if !ok {
return errors.New("hazelcast error: cannot cast message to byte array")
}
return l.handleMessageObject(msg)
}

View File

@ -114,6 +114,7 @@ func (k *Kafka) Init(metadata pubsub.Metadata) error {
k.topics = make(map[string]bool)
k.logger.Debug("Kafka message bus initialization complete")
return nil
}
@ -154,7 +155,6 @@ func (k *Kafka) closeSubscripionResources() {
if k.cg != nil {
k.cancel()
err := k.cg.Close()
if err != nil {
k.logger.Errorf("Error closing consumer group: %v", err)
}
@ -175,7 +175,6 @@ func (k *Kafka) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler) e
k.closeSubscripionResources()
cg, err := sarama.NewConsumerGroup(k.brokers, k.consumerGroup, k.config)
if err != nil {
return err
}
@ -195,7 +194,6 @@ func (k *Kafka) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler) e
defer func() {
k.logger.Debugf("Closing ConsumerGroup for topics: %v", topics)
err := k.cg.Close()
if err != nil {
k.logger.Errorf("Error closing consumer group: %v", err)
}
@ -219,6 +217,7 @@ func (k *Kafka) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler) e
}()
<-ready
return nil
}
@ -245,13 +244,12 @@ func (k *Kafka) getKafkaMetadata(metadata pubsub.Metadata) (*kafkaMetadata, erro
return nil, errors.New("kafka error: 'authRequired' attribute was empty")
}
validAuthRequired, err := strconv.ParseBool(val)
if err != nil {
return nil, errors.New("kafka error: invalid value for 'authRequired' attribute")
}
meta.AuthRequired = validAuthRequired
//ignore SASL properties if authRequired is false
// ignore SASL properties if authRequired is false
if meta.AuthRequired {
if val, ok := metadata.Properties["saslUsername"]; ok && val != "" {
meta.SaslUsername = val
@ -283,6 +281,7 @@ func (k *Kafka) getSyncProducer(meta *kafkaMetadata) (sarama.SyncProducer, error
if err != nil {
return nil, err
}
return producer, nil
}
@ -293,8 +292,15 @@ func updateAuthInfo(config *sarama.Config, saslUsername, saslPassword string) {
config.Net.SASL.Mechanism = sarama.SASLTypePlaintext
config.Net.TLS.Enable = true
// nolint: gosec
config.Net.TLS.Config = &tls.Config{
//InsecureSkipVerify: true,
// InsecureSkipVerify: true,
ClientAuth: 0,
}
}
func (k *Kafka) Close() error {
k.closeSubscripionResources()
return k.producer.Close()
}

View File

@ -8,9 +8,8 @@ package kafka
import (
"testing"
"github.com/dapr/dapr/pkg/logger"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
"github.com/stretchr/testify/assert"
)

View File

@ -15,11 +15,10 @@ import (
"strconv"
"time"
"github.com/google/uuid"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
mqtt "github.com/eclipse/paho.mqtt.golang"
"github.com/google/uuid"
)
const (
@ -45,9 +44,12 @@ const (
// mqttPubSub type allows sending and receiving data to/from MQTT broker.
type mqttPubSub struct {
client mqtt.Client
producer mqtt.Client
consumer mqtt.Client
metadata *metadata
logger logger.Logger
topics map[string]byte
cancel context.CancelFunc
}
// NewMQTTPubSub returns a new mqttPubSub instance.
@ -58,6 +60,7 @@ func NewMQTTPubSub(logger logger.Logger) pubsub.PubSub {
// isValidPEM validates the provided input has PEM formatted block.
func isValidPEM(val string) bool {
block, _ := pem.Decode([]byte(val))
return block != nil
}
@ -134,18 +137,18 @@ func (m *mqttPubSub) Init(metadata pubsub.Metadata) error {
}
m.metadata = mqttMeta
uri, err := url.Parse(m.metadata.url)
if err != nil {
return err
}
client, err := m.connect(uri)
// mqtt broker allows only one connection at a given time from a clientID.
producerClientID := fmt.Sprintf("%s-producer", m.metadata.clientID)
p, err := m.connect(producerClientID)
if err != nil {
return err
}
m.client = client
m.producer = p
m.topics = make(map[string]byte)
m.logger.Debug("mqtt message bus initialization complete")
return nil
}
@ -153,29 +156,60 @@ func (m *mqttPubSub) Init(metadata pubsub.Metadata) error {
func (m *mqttPubSub) Publish(req *pubsub.PublishRequest) error {
m.logger.Debugf("mqtt publishing topic %s with data: %v", req.Topic, req.Data)
token := m.client.Publish(req.Topic, m.metadata.qos, m.metadata.retain, req.Data)
token := m.producer.Publish(req.Topic, m.metadata.qos, m.metadata.retain, req.Data)
if !token.WaitTimeout(defaultWait) || token.Error() != nil {
return fmt.Errorf("mqtt error from publish: %v", token.Error())
}
return nil
}
// Subscribe to the mqtt pub sub topic.
func (m *mqttPubSub) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler) error {
token := m.client.Subscribe(
req.Topic,
m.metadata.qos,
func(client mqtt.Client, mqttMsg mqtt.Message) {
handler(context.Background(), &pubsub.NewMessage{Topic: req.Topic, Data: mqttMsg.Payload()})
})
if err := token.Error(); err != nil {
return fmt.Errorf("mqtt error from subscribe: %v", err)
m.topics[req.Topic] = m.metadata.qos
// reset synchronization
if m.consumer != nil {
m.logger.Warnf("re-initializing the subscriber")
m.cancel()
m.consumer.Disconnect(0)
}
// mqtt broker allows only one connection at a given time from a clientID.
consumerClientID := fmt.Sprintf("%s-consumer", m.metadata.clientID)
c, err := m.connect(consumerClientID)
if err != nil {
return err
}
m.consumer = c
ctx, cancel := context.WithCancel(context.Background())
m.cancel = cancel
go func() {
token := m.consumer.SubscribeMultiple(
m.topics,
func(client mqtt.Client, mqttMsg mqtt.Message) {
handler(&pubsub.NewMessage{Topic: req.Topic, Data: mqttMsg.Payload()})
})
if err := token.Error(); err != nil {
m.logger.Errorf("mqtt error from subscribe: %v", err)
}
if ctx.Err() != nil {
return
}
}()
return nil
}
func (m *mqttPubSub) connect(uri *url.URL) (mqtt.Client, error) {
opts := m.createClientOptions(uri)
func (m *mqttPubSub) connect(clientID string) (mqtt.Client, error) {
uri, err := url.Parse(m.metadata.url)
if err != nil {
return nil, err
}
opts := m.createClientOptions(uri, clientID)
client := mqtt.NewClient(opts)
token := client.Connect()
for !token.WaitTimeout(defaultWait) {
@ -183,16 +217,18 @@ func (m *mqttPubSub) connect(uri *url.URL) (mqtt.Client, error) {
if err := token.Error(); err != nil {
return nil, err
}
return client, nil
}
func (m *mqttPubSub) NewTLSConfig() *tls.Config {
func (m *mqttPubSub) newTLSConfig() *tls.Config {
tlsConfig := new(tls.Config)
if m.metadata.clientCert != "" && m.metadata.clientKey != "" {
cert, err := tls.X509KeyPair([]byte(m.metadata.clientCert), []byte(m.metadata.clientKey))
if err != nil {
m.logger.Warnf("unable to load client certificate and key pair. Err: %v", err)
return tlsConfig
}
tlsConfig.Certificates = []tls.Certificate{cert}
@ -208,15 +244,23 @@ func (m *mqttPubSub) NewTLSConfig() *tls.Config {
return tlsConfig
}
func (m *mqttPubSub) createClientOptions(uri *url.URL) *mqtt.ClientOptions {
func (m *mqttPubSub) createClientOptions(uri *url.URL, clientID string) *mqtt.ClientOptions {
opts := mqtt.NewClientOptions()
opts.SetClientID(m.metadata.clientID)
opts.SetClientID(clientID)
opts.SetCleanSession(m.metadata.cleanSession)
opts.AddBroker(uri.Scheme + "://" + uri.Host)
opts.SetUsername(uri.User.Username())
password, _ := uri.User.Password()
opts.SetPassword(password)
// tls config
opts.SetTLSConfig(m.NewTLSConfig())
opts.SetTLSConfig(m.newTLSConfig())
return opts
}
func (m *mqttPubSub) Close() error {
m.consumer.Disconnect(0)
m.producer.Disconnect(0)
return nil
}

View File

@ -63,6 +63,7 @@ func (n *natsPubSub) Init(metadata pubsub.Metadata) error {
n.logger.Debugf("connected to nats at %s", m.natsURL)
n.natsConn = natsConn
return nil
}
@ -71,6 +72,7 @@ func (n *natsPubSub) Publish(req *pubsub.PublishRequest) error {
if err != nil {
return fmt.Errorf("nats: error from publish: %s", err)
}
return nil
}
@ -85,3 +87,9 @@ func (n *natsPubSub) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handl
return nil
}
func (n *natsPubSub) Close() error {
n.natsConn.Close()
return nil
}

View File

@ -9,9 +9,8 @@ import (
"errors"
"testing"
"github.com/stretchr/testify/assert"
"github.com/dapr/components-contrib/pubsub"
"github.com/stretchr/testify/assert"
)
func TestParseNATSMetadata(t *testing.T) {

View File

@ -17,7 +17,7 @@ import (
"time"
"github.com/dapr/components-contrib/pubsub"
"github.com/nats-io/gnatsd/logger"
"github.com/dapr/dapr/pkg/logger"
nats "github.com/nats-io/nats.go"
stan "github.com/nats-io/stan.go"
"github.com/nats-io/stan.go/pb"
@ -51,7 +51,7 @@ const (
)
const (
consumerID = "consumerID" //passed in by Dapr runtime
consumerID = "consumerID" // passed in by Dapr runtime
subscriptionType = "subscriptionType"
)
@ -165,7 +165,10 @@ func (n *natsStreamingPubSub) Init(metadata pubsub.Metadata) error {
if err != nil {
return fmt.Errorf("nats-streaming: error connecting to nats streaming server %s: %s", m.natsStreamingClusterID, err)
}
n.logger.Debugf("connected to natsstreaming at %s", m.natsURL)
n.natStreamingConn = natStreamingConn
return nil
}
@ -174,6 +177,7 @@ func (n *natsStreamingPubSub) Publish(req *pubsub.PublishRequest) error {
if err != nil {
return fmt.Errorf("nats-streaming: error from publish: %s", err)
}
return nil
}
@ -200,6 +204,11 @@ func (n *natsStreamingPubSub) Subscribe(req pubsub.SubscribeRequest, handler pub
if err != nil {
return fmt.Errorf("nats-streaming: subscribe error %s", err)
}
if n.metadata.subscriptionType == subscriptionTypeTopic {
n.logger.Debugf("nats: subscribed to subject %s", req.Topic)
} else if n.metadata.subscriptionType == subscriptionTypeQueueGroup {
n.logger.Debugf("nats: subscribed to subject %s with queue group %s", req.Topic, n.metadata.natsQueueGroupName)
}
return nil
}
@ -214,13 +223,13 @@ func (n *natsStreamingPubSub) subscriptionOptions() ([]stan.SubscriptionOption,
switch {
case n.metadata.deliverNew == deliverNewTrue:
options = append(options, stan.StartAt(pb.StartPosition_NewOnly))
case n.metadata.startAtSequence >= 1: //messages index start from 1, this is a valid check
case n.metadata.startAtSequence >= 1: // messages index start from 1, this is a valid check
options = append(options, stan.StartAtSequence(n.metadata.startAtSequence))
case n.metadata.startWithLastReceived == startWithLastReceivedTrue:
options = append(options, stan.StartWithLastReceived())
case n.metadata.deliverAll == deliverAllTrue:
options = append(options, stan.DeliverAllAvailable())
case n.metadata.startAtTimeDelta > (1 * time.Nanosecond): //as long as its a valid time.Duration
case n.metadata.startAtTimeDelta > (1 * time.Nanosecond): // as long as its a valid time.Duration
options = append(options, stan.StartAtTimeDelta(n.metadata.startAtTimeDelta))
case n.metadata.startAtTime != "":
if n.metadata.startAtTimeFormat != "" {
@ -251,3 +260,7 @@ func genRandomString(n int) string {
return clientID
}
func (n *natsStreamingPubSub) Close() error {
return n.natStreamingConn.Close()
}

View File

@ -10,9 +10,8 @@ import (
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/dapr/components-contrib/pubsub"
"github.com/stretchr/testify/assert"
)
func TestParseNATSStreamingForMetadataMandatoryOptionsMissing(t *testing.T) {
@ -127,50 +126,65 @@ func TestParseNATSStreamingMetadataForValidSubscriptionOptions(t *testing.T) {
tests := []test{
{"using startWithLastReceived", map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
startWithLastReceived: "true",
{
"using startWithLastReceived",
map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
startWithLastReceived: "true",
},
"startWithLastReceived", "true",
},
"startWithLastReceived", "true"},
{"using deliverAll", map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
deliverAll: "true",
{
"using deliverAll",
map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
deliverAll: "true",
},
"deliverAll", "true",
},
"deliverAll", "true"},
{"using deliverNew", map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
deliverNew: "true",
{
"using deliverNew",
map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
deliverNew: "true",
},
"deliverNew", "true",
},
"deliverNew", "true"},
{"using startAtSequence", map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
startAtSequence: "42",
{
"using startAtSequence",
map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
startAtSequence: "42",
},
"startAtSequence", "42",
},
"startAtSequence", "42"},
{"using startAtTimeDelta", map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
startAtTimeDelta: "1h",
{
"using startAtTimeDelta",
map[string]string{
natsURL: "nats://foo.bar:4222",
natsStreamingClusterID: "testcluster",
consumerID: "consumer1",
subscriptionType: "topic",
startAtTimeDelta: "1h",
},
"startAtTimeDelta", "1h",
},
"startAtTimeDelta", "1h"},
}
for _, _test := range tests {
@ -196,6 +210,7 @@ func TestParseNATSStreamingMetadataForValidSubscriptionOptions(t *testing.T) {
})
}
}
func TestParseNATSStreamingMetadata(t *testing.T) {
t.Run("mandatory metadata provided", func(t *testing.T) {
fakeProperties := map[string]string{
@ -262,9 +277,9 @@ func TestParseNATSStreamingMetadata(t *testing.T) {
assert.NotEmpty(t, m.subscriptionType)
assert.NotEmpty(t, m.natsQueueGroupName)
assert.NotEmpty(t, m.startAtSequence)
//startWithLastReceived ignored
// startWithLastReceived ignored
assert.Empty(t, m.startWithLastReceived)
//deliverAll will be ignored
// deliverAll will be ignored
assert.Empty(t, m.deliverAll)
assert.Equal(t, fakeProperties[natsURL], m.natsURL)
@ -330,7 +345,7 @@ func TestSubscriptionOptionsForInvalidOptions(t *testing.T) {
}
func TestSubscriptionOptions(t *testing.T) {
//general
// general
t.Run("manual ACK option is present by default", func(t *testing.T) {
natsStreaming := natsStreamingPubSub{metadata: metadata{}}
opts, err := natsStreaming.subscriptionOptions()

View File

@ -11,7 +11,8 @@ import "context"
type PubSub interface {
Init(metadata Metadata) error
Publish(req *PublishRequest) error
Subscribe(req SubscribeRequest, handler Handler) error
Subscribe(req SubscribeRequest, handler pubsub.Handler) error
Close() error
}
// Handler is the handler used to invoke the app handler

View File

@ -8,9 +8,8 @@ import (
"time"
"github.com/apache/pulsar-client-go/pulsar"
"github.com/dapr/dapr/pkg/logger"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
)
const (
@ -66,6 +65,7 @@ func (p *Pulsar) Init(metadata pubsub.Metadata) error {
p.client = client
p.metadata = *m
return nil
}
@ -126,3 +126,9 @@ func (p *Pulsar) HandleMessage(m pulsar.ConsumerMessage, topic string, handler p
m.Ack(m.Message)
}
}
func (p *Pulsar) Close() error {
p.client.Close()
return nil
}

View File

@ -3,9 +3,8 @@ package pulsar
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/dapr/components-contrib/pubsub"
"github.com/stretchr/testify/assert"
)
func TestParsePulsarMetadata(t *testing.T) {

View File

@ -17,7 +17,7 @@ func getFakeProperties() map[string]string {
}
func TestCreateMetadata(t *testing.T) {
var booleanFlagTests = []struct {
booleanFlagTests := []struct {
in string
expected bool
}{
@ -82,7 +82,7 @@ func TestCreateMetadata(t *testing.T) {
assert.Empty(t, m.consumerID)
})
var invalidDeliveryModes = []string{"3", "10", "-1"}
invalidDeliveryModes := []string{"3", "10", "-1"}
for _, deliveryMode := range invalidDeliveryModes {
t.Run(fmt.Sprintf("deliveryMode value=%s", deliveryMode), func(t *testing.T) {

View File

@ -58,6 +58,7 @@ func (r *rabbitMQ) Init(metadata pubsub.Metadata) error {
r.connection = conn
r.channel = ch
return nil
}
@ -111,7 +112,6 @@ func (r *rabbitMQ) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Handler
false, // noWait
nil,
)
if err != nil {
return err
}
@ -170,3 +170,7 @@ func (r *rabbitMQ) ensureExchangeDeclared(exchange string) error {
return nil
}
func (r *rabbitMQ) Close() error {
return r.connection.Close()
}

View File

@ -13,9 +13,8 @@ import (
"strconv"
"time"
"github.com/dapr/dapr/pkg/logger"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
"github.com/go-redis/redis/v7"
)
@ -97,6 +96,7 @@ func (r *redisStreams) Init(metadata pubsub.Metadata) error {
}
r.client = client
return nil
}
@ -118,6 +118,7 @@ func (r *redisStreams) Subscribe(req pubsub.SubscribeRequest, handler pubsub.Han
r.logger.Warnf("redis streams: %s", err)
}
go r.beginReadingFromStream(req.Topic, r.metadata.consumerID, handler)
return nil
}
@ -164,11 +165,16 @@ func (r *redisStreams) beginReadingFromStream(stream, consumerID string, handler
streams, err := r.readFromStream(stream, consumerID, start)
if err != nil {
r.logger.Errorf("redis streams: error reading from stream %s: %s", stream, err)
return
}
r.processStreams(consumerID, streams, handler)
//continue with new non received items
// continue with new non received items
start = ">"
}
}
func (r *redisStreams) Close() error {
return r.client.Close()
}

View File

@ -12,11 +12,10 @@ import (
"testing"
"time"
"github.com/go-redis/redis/v7"
"github.com/stretchr/testify/assert"
"github.com/dapr/components-contrib/pubsub"
"github.com/dapr/dapr/pkg/logger"
"github.com/go-redis/redis/v7"
"github.com/stretchr/testify/assert"
)
func getFakeProperties() map[string]string {
@ -137,5 +136,6 @@ func generateRedisStreamTestData(topicCount, messageCount int, data string) []re
Messages: xmessageArray,
}
}
return redisStreams
}

Some files were not shown because too many files have changed in this diff Show More