Merge branch 'master' into mark-as-unread
Этот коммит содержится в:
12
Makefile
12
Makefile
@@ -154,7 +154,7 @@ govet: ## Runs govet against all packages.
|
|||||||
env GO111MODULE=off $(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
|
env GO111MODULE=off $(GO) get golang.org/x/tools/go/analysis/passes/shadow/cmd/shadow
|
||||||
$(GO) vet $(GOFLAGS) $(ALL_PACKAGES) || exit 1
|
$(GO) vet $(GOFLAGS) $(ALL_PACKAGES) || exit 1
|
||||||
$(GO) vet -vettool=$(GOPATH)/bin/shadow $(GOFLAGS) $(ALL_PACKAGES) || exit 1
|
$(GO) vet -vettool=$(GOPATH)/bin/shadow $(GOFLAGS) $(ALL_PACKAGES) || exit 1
|
||||||
$(GO) run $(GOFLAGS) plugin/checker/main.go
|
$(GO) run $(GOFLAGS) ./plugin/checker
|
||||||
|
|
||||||
gofmt: ## Runs gofmt against all packages.
|
gofmt: ## Runs gofmt against all packages.
|
||||||
@echo Running GOFMT
|
@echo Running GOFMT
|
||||||
@@ -173,17 +173,15 @@ gofmt: ## Runs gofmt against all packages.
|
|||||||
done
|
done
|
||||||
@echo "gofmt success"; \
|
@echo "gofmt success"; \
|
||||||
|
|
||||||
golangci-lint:
|
golangci-lint: ## Run golangci-lint on codebasis
|
||||||
# https://stackoverflow.com/a/677212/1027058 (check if a command exists or not)
|
# https://stackoverflow.com/a/677212/1027058 (check if a command exists or not)
|
||||||
# https://github.com/golangci/golangci-lint#binary-release
|
@if ! [ -x "$$(command -v golangci-lintt)" ]; then \
|
||||||
# It is recommended to NOT use go get, but instead use a binary release pinned to a version.
|
echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \
|
||||||
@if ! [ -x "$$(command -v golangci-lint)" ]; then \
|
|
||||||
echo "golangci-lint is not installed. Please run: curl -sfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh| sh -s -- -b $(GOPATH)/bin v1.21.0"; \
|
|
||||||
exit 1; \
|
exit 1; \
|
||||||
fi; \
|
fi; \
|
||||||
|
|
||||||
@echo Running golangci-lint
|
@echo Running golangci-lint
|
||||||
$(GOPATH)/bin/golangci-lint run
|
golangci-lint run
|
||||||
|
|
||||||
megacheck: ## Run megacheck on codebasis
|
megacheck: ## Run megacheck on codebasis
|
||||||
env GO111MODULE=off go get -u honnef.co/go/tools/cmd/megacheck
|
env GO111MODULE=off go get -u honnef.co/go/tools/cmd/megacheck
|
||||||
|
|||||||
@@ -9,12 +9,12 @@ import (
|
|||||||
_ "image/gif"
|
_ "image/gif"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/app"
|
"github.com/mattermost/mattermost-server/app"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
"github.com/mattermost/mattermost-server/utils"
|
"github.com/mattermost/mattermost-server/utils"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateEmoji(t *testing.T) {
|
func TestCreateEmoji(t *testing.T) {
|
||||||
@@ -46,9 +46,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
// try to create a valid gif emoji when they're enabled
|
// try to create a valid gif emoji when they're enabled
|
||||||
newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
|
newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if newEmoji.Name != emoji.Name {
|
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||||
t.Fatal("create with wrong name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create an emoji with a duplicate name
|
// try to create an emoji with a duplicate name
|
||||||
emoji2 := &model.Emoji{
|
emoji2 := &model.Emoji{
|
||||||
@@ -67,9 +65,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
|
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if newEmoji.Name != emoji.Name {
|
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||||
t.Fatal("create with wrong name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create a valid jpeg emoji
|
// try to create a valid jpeg emoji
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -79,9 +75,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
|
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.gif")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if newEmoji.Name != emoji.Name {
|
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||||
t.Fatal("create with wrong name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create a valid png emoji
|
// try to create a valid png emoji
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -91,9 +85,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
|
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.gif")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if newEmoji.Name != emoji.Name {
|
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||||
t.Fatal("create with wrong name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create an emoji that's too wide
|
// try to create an emoji that's too wide
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -103,9 +95,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
|
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if newEmoji.Name != emoji.Name {
|
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||||
t.Fatal("create with wrong name")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create an emoji that's too wide
|
// try to create an emoji that's too wide
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -114,9 +104,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, app.MaxEmojiOriginalWidth+1), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, app.MaxEmojiOriginalWidth+1), "image.gif")
|
||||||
if resp.Error == nil {
|
require.Error(t, resp.Error, "should fail - emoji is too wide")
|
||||||
t.Fatal("should fail - emoji is too wide")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create an emoji that's too tall
|
// try to create an emoji that's too tall
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -125,9 +113,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, app.MaxEmojiOriginalHeight+1, 10), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, app.MaxEmojiOriginalHeight+1, 10), "image.gif")
|
||||||
if resp.Error == nil {
|
require.Error(t, resp.Error, "should fail - emoji is too tall")
|
||||||
t.Fatal("should fail - emoji is too tall")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create an emoji that's too large
|
// try to create an emoji that's too large
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -136,9 +122,7 @@ func TestCreateEmoji(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif")
|
_, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif")
|
||||||
if resp.Error == nil {
|
require.Error(t, resp.Error, "should fail - emoji is too big")
|
||||||
t.Fatal("should fail - emoji is too big")
|
|
||||||
}
|
|
||||||
|
|
||||||
// try to create an emoji with data that isn't an image
|
// try to create an emoji with data that isn't an image
|
||||||
emoji = &model.Emoji{
|
emoji = &model.Emoji{
|
||||||
@@ -224,9 +208,7 @@ func TestGetEmojiList(t *testing.T) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !found {
|
require.Truef(t, found, "failed to get emoji with id %v, %v", emoji.Id, len(listEmoji))
|
||||||
t.Fatalf("failed to get emoji with id %v, %v", emoji.Id, len(listEmoji))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_, resp = Client.DeleteEmoji(emojis[0].Id)
|
_, resp = Client.DeleteEmoji(emojis[0].Id)
|
||||||
@@ -245,16 +227,12 @@ func TestGetEmojiList(t *testing.T) {
|
|||||||
listEmoji, resp = Client.GetEmojiList(0, 1)
|
listEmoji, resp = Client.GetEmojiList(0, 1)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(listEmoji) != 1 {
|
require.Len(t, listEmoji, 1, "should only return 1")
|
||||||
t.Fatal("should only return 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
listEmoji, resp = Client.GetSortedEmojiList(0, 100, model.EMOJI_SORT_BY_NAME)
|
listEmoji, resp = Client.GetSortedEmojiList(0, 100, model.EMOJI_SORT_BY_NAME)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(listEmoji) == 0 {
|
require.Greater(t, len(listEmoji), 0, "should return more than 0")
|
||||||
t.Fatal("should return more than 0")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDeleteEmoji(t *testing.T) {
|
func TestDeleteEmoji(t *testing.T) {
|
||||||
@@ -283,14 +261,11 @@ func TestDeleteEmoji(t *testing.T) {
|
|||||||
|
|
||||||
ok, resp := Client.DeleteEmoji(newEmoji.Id)
|
ok, resp := Client.DeleteEmoji(newEmoji.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if !ok {
|
require.True(t, ok, "delete did not return OK")
|
||||||
t.Fatal("should return true")
|
|
||||||
} else {
|
_, resp = Client.GetEmoji(newEmoji.Id)
|
||||||
_, err := Client.GetEmoji(newEmoji.Id)
|
require.NotNil(t, resp, "nil response")
|
||||||
if err == nil {
|
require.Error(t, resp.Error, "expected error fetching deleted emoji")
|
||||||
t.Fatal("should not return the emoji it was deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Admin can delete other users emoji
|
//Admin can delete other users emoji
|
||||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
|
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
|
||||||
@@ -298,14 +273,11 @@ func TestDeleteEmoji(t *testing.T) {
|
|||||||
|
|
||||||
ok, resp = th.SystemAdminClient.DeleteEmoji(newEmoji.Id)
|
ok, resp = th.SystemAdminClient.DeleteEmoji(newEmoji.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if !ok {
|
require.True(t, ok, "delete did not return OK")
|
||||||
t.Fatal("should return true")
|
|
||||||
} else {
|
_, resp = th.SystemAdminClient.GetEmoji(newEmoji.Id)
|
||||||
_, err := th.SystemAdminClient.GetEmoji(newEmoji.Id)
|
require.NotNil(t, resp, "nil response")
|
||||||
if err == nil {
|
require.Error(t, resp.Error, "expected error fetching deleted emoji")
|
||||||
t.Fatal("should not return the emoji it was deleted")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to delete just deleted emoji
|
// Try to delete just deleted emoji
|
||||||
_, resp = Client.DeleteEmoji(newEmoji.Id)
|
_, resp = Client.DeleteEmoji(newEmoji.Id)
|
||||||
@@ -445,9 +417,7 @@ func TestGetEmoji(t *testing.T) {
|
|||||||
|
|
||||||
emoji, resp = Client.GetEmoji(newEmoji.Id)
|
emoji, resp = Client.GetEmoji(newEmoji.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if emoji.Id != newEmoji.Id {
|
require.Equal(t, newEmoji.Id, emoji.Id, "wrong emoji was returned")
|
||||||
t.Fatal("wrong emoji was returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.GetEmoji(model.NewId())
|
_, resp = Client.GetEmoji(model.NewId())
|
||||||
CheckNotFoundStatus(t, resp)
|
CheckNotFoundStatus(t, resp)
|
||||||
@@ -506,15 +476,11 @@ func TestGetEmojiImage(t *testing.T) {
|
|||||||
|
|
||||||
emojiImage, resp := Client.GetEmojiImage(emoji1.Id)
|
emojiImage, resp := Client.GetEmojiImage(emoji1.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if len(emojiImage) <= 0 {
|
require.Greater(t, len(emojiImage), 0, "should return the image")
|
||||||
t.Fatal("should return the image")
|
|
||||||
}
|
|
||||||
_, imageType, err := image.DecodeConfig(bytes.NewReader(emojiImage))
|
_, imageType, err := image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
require.Equal(t, imageType, "gif", "expected gif")
|
||||||
} else if imageType != "gif" {
|
|
||||||
t.Fatal("should've received gif data")
|
|
||||||
}
|
|
||||||
|
|
||||||
emoji2 := &model.Emoji{
|
emoji2 := &model.Emoji{
|
||||||
CreatorId: th.BasicUser.Id,
|
CreatorId: th.BasicUser.Id,
|
||||||
@@ -526,15 +492,11 @@ func TestGetEmojiImage(t *testing.T) {
|
|||||||
|
|
||||||
emojiImage, resp = Client.GetEmojiImage(emoji2.Id)
|
emojiImage, resp = Client.GetEmojiImage(emoji2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if len(emojiImage) <= 0 {
|
require.Greater(t, len(emojiImage), 0, "no image returned")
|
||||||
t.Fatal("should return the image")
|
|
||||||
}
|
|
||||||
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||||
if err != nil {
|
require.NoError(t, err, "unable to indentify received image")
|
||||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
require.Equal(t, imageType, "gif", "expected gif")
|
||||||
} else if imageType != "gif" {
|
|
||||||
t.Fatal("should've received gif data")
|
|
||||||
}
|
|
||||||
|
|
||||||
emoji3 := &model.Emoji{
|
emoji3 := &model.Emoji{
|
||||||
CreatorId: th.BasicUser.Id,
|
CreatorId: th.BasicUser.Id,
|
||||||
@@ -545,15 +507,11 @@ func TestGetEmojiImage(t *testing.T) {
|
|||||||
|
|
||||||
emojiImage, resp = Client.GetEmojiImage(emoji3.Id)
|
emojiImage, resp = Client.GetEmojiImage(emoji3.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if len(emojiImage) <= 0 {
|
require.Greater(t, len(emojiImage), 0, "no image returned")
|
||||||
t.Fatal("should return the image")
|
|
||||||
}
|
|
||||||
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||||
if err != nil {
|
require.NoError(t, err, "unable to indentify received image")
|
||||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
require.Equal(t, imageType, "jpeg", "expected jpeg")
|
||||||
} else if imageType != "jpeg" {
|
|
||||||
t.Fatal("should've received gif data")
|
|
||||||
}
|
|
||||||
|
|
||||||
emoji4 := &model.Emoji{
|
emoji4 := &model.Emoji{
|
||||||
CreatorId: th.BasicUser.Id,
|
CreatorId: th.BasicUser.Id,
|
||||||
@@ -564,15 +522,11 @@ func TestGetEmojiImage(t *testing.T) {
|
|||||||
|
|
||||||
emojiImage, resp = Client.GetEmojiImage(emoji4.Id)
|
emojiImage, resp = Client.GetEmojiImage(emoji4.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if len(emojiImage) <= 0 {
|
require.Greater(t, len(emojiImage), 0, "no image returned")
|
||||||
t.Fatal("should return the image")
|
|
||||||
}
|
|
||||||
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||||
if err != nil {
|
require.NoError(t, err, "unable to idenitify received image")
|
||||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
require.Equal(t, imageType, "png", "expected png")
|
||||||
} else if imageType != "png" {
|
|
||||||
t.Fatal("should've received gif data")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.DeleteEmoji(emoji4.Id)
|
_, resp = Client.DeleteEmoji(emoji4.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|||||||
@@ -27,8 +27,11 @@ func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
assert.NotEmpty(th.t, string(bb))
|
assert.NotEmpty(th.t, string(bb))
|
||||||
poir := model.PostActionIntegrationRequestFromJson(bytes.NewReader(bb))
|
poir := model.PostActionIntegrationRequestFromJson(bytes.NewReader(bb))
|
||||||
assert.NotEmpty(th.t, poir.UserId)
|
assert.NotEmpty(th.t, poir.UserId)
|
||||||
|
assert.NotEmpty(th.t, poir.UserName)
|
||||||
assert.NotEmpty(th.t, poir.ChannelId)
|
assert.NotEmpty(th.t, poir.ChannelId)
|
||||||
assert.Empty(th.t, poir.TeamId)
|
assert.NotEmpty(th.t, poir.ChannelName)
|
||||||
|
assert.NotEmpty(th.t, poir.TeamId)
|
||||||
|
assert.NotEmpty(th.t, poir.TeamName)
|
||||||
assert.NotEmpty(th.t, poir.PostId)
|
assert.NotEmpty(th.t, poir.PostId)
|
||||||
assert.NotEmpty(th.t, poir.TriggerId)
|
assert.NotEmpty(th.t, poir.TriggerId)
|
||||||
assert.Equal(th.t, "button", poir.Type)
|
assert.Equal(th.t, "button", poir.Type)
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,22 +17,22 @@ func TestGetOldClientLicense(t *testing.T) {
|
|||||||
license, resp := Client.GetOldClientLicense("")
|
license, resp := Client.GetOldClientLicense("")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(license["IsLicensed"]) == 0 {
|
require.NotEqual(t, license["IsLicensed"], "", "license not returned correctly")
|
||||||
t.Fatal("license not returned correctly")
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.Logout()
|
Client.Logout()
|
||||||
|
|
||||||
_, resp = Client.GetOldClientLicense("")
|
_, resp = Client.GetOldClientLicense("")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if _, err := Client.DoApiGet("/license/client", ""); err == nil || err.StatusCode != http.StatusNotImplemented {
|
_, err := Client.DoApiGet("/license/client", "")
|
||||||
t.Fatal("should have errored with 501")
|
require.Error(t, err, "get /license/client did not return an error")
|
||||||
}
|
require.Equal(t, err.StatusCode, http.StatusNotImplemented,
|
||||||
|
"expected 501 Not Implemented")
|
||||||
|
|
||||||
if _, err := Client.DoApiGet("/license/client?format=junk", ""); err == nil || err.StatusCode != http.StatusBadRequest {
|
_, err = Client.DoApiGet("/license/client?format=junk", "")
|
||||||
t.Fatal("should have errored with 400")
|
require.Error(t, err, "get /license/client?format=junk did not return an error")
|
||||||
}
|
require.Equal(t, err.StatusCode, http.StatusBadRequest,
|
||||||
|
"expected 400 Bad Request")
|
||||||
|
|
||||||
license, resp = th.SystemAdminClient.GetOldClientLicense("")
|
license, resp = th.SystemAdminClient.GetOldClientLicense("")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|||||||
@@ -6,10 +6,11 @@ package api4
|
|||||||
import (
|
import (
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateOAuthApp(t *testing.T) {
|
func TestCreateOAuthApp(t *testing.T) {
|
||||||
@@ -35,21 +36,14 @@ func TestCreateOAuthApp(t *testing.T) {
|
|||||||
rapp, resp := AdminClient.CreateOAuthApp(oapp)
|
rapp, resp := AdminClient.CreateOAuthApp(oapp)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
CheckCreatedStatus(t, resp)
|
CheckCreatedStatus(t, resp)
|
||||||
|
assert.Equal(t, oapp.Name, rapp.Name, "names did not match")
|
||||||
if rapp.Name != oapp.Name {
|
assert.Equal(t, oapp.IsTrusted, rapp.IsTrusted, "trusted did no match")
|
||||||
t.Fatal("names did not match")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rapp.IsTrusted != oapp.IsTrusted {
|
|
||||||
t.Fatal("trusted did no match")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoke permission from regular users.
|
// Revoke permission from regular users.
|
||||||
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||||
|
|
||||||
_, resp = Client.CreateOAuthApp(oapp)
|
_, resp = Client.CreateOAuthApp(oapp)
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|
||||||
// Grant permission to regular users.
|
// Grant permission to regular users.
|
||||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||||
|
|
||||||
@@ -57,23 +51,15 @@ func TestCreateOAuthApp(t *testing.T) {
|
|||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
CheckCreatedStatus(t, resp)
|
CheckCreatedStatus(t, resp)
|
||||||
|
|
||||||
if rapp.IsTrusted {
|
assert.False(t, rapp.IsTrusted, "trusted should be false - created by non admin")
|
||||||
t.Fatal("trusted should be false - created by non admin")
|
|
||||||
}
|
|
||||||
|
|
||||||
oapp.Name = ""
|
oapp.Name = ""
|
||||||
_, resp = AdminClient.CreateOAuthApp(oapp)
|
_, resp = AdminClient.CreateOAuthApp(oapp)
|
||||||
CheckBadRequestStatus(t, resp)
|
CheckBadRequestStatus(t, resp)
|
||||||
|
|
||||||
if r, err := Client.DoApiPost("/oauth/apps", "garbage"); err == nil {
|
r, err := Client.DoApiPost("/oauth/apps", "garbage")
|
||||||
t.Fatal("should have failed")
|
require.Error(t, err, "expected error from garbage post")
|
||||||
} else {
|
assert.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||||
if r.StatusCode != http.StatusBadRequest {
|
|
||||||
t.Log("actual: " + strconv.Itoa(r.StatusCode))
|
|
||||||
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
|
|
||||||
t.Fatal("wrong status code")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Client.Logout()
|
Client.Logout()
|
||||||
_, resp = Client.CreateOAuthApp(oapp)
|
_, resp = Client.CreateOAuthApp(oapp)
|
||||||
@@ -122,54 +108,22 @@ func TestUpdateOAuthApp(t *testing.T) {
|
|||||||
|
|
||||||
updatedApp, resp := AdminClient.UpdateOAuthApp(oapp)
|
updatedApp, resp := AdminClient.UpdateOAuthApp(oapp)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, oapp.Id, updatedApp.Id, "Id should have not updated")
|
||||||
if updatedApp.Id != oapp.Id {
|
assert.Equal(t, oapp.CreatorId, updatedApp.CreatorId, "CreatorId should have not updated")
|
||||||
t.Fatal("Id should have not updated")
|
assert.Equal(t, oapp.CreateAt, updatedApp.CreateAt, "CreateAt should have not updated")
|
||||||
}
|
assert.NotEqual(t, oapp.UpdateAt, updatedApp.UpdateAt, "UpdateAt should have updated")
|
||||||
|
assert.Equal(t, oapp.ClientSecret, updatedApp.ClientSecret, "ClientSecret should have not updated")
|
||||||
if updatedApp.CreatorId != oapp.CreatorId {
|
assert.Equal(t, oapp.Name, updatedApp.Name, "Name should have updated")
|
||||||
t.Fatal("CreatorId should have not updated")
|
assert.Equal(t, oapp.Description, updatedApp.Description, "Description should have updated")
|
||||||
}
|
assert.Equal(t, oapp.IconURL, updatedApp.IconURL, "IconURL should have updated")
|
||||||
|
|
||||||
if updatedApp.CreateAt != oapp.CreateAt {
|
|
||||||
t.Fatal("CreateAt should have not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedApp.UpdateAt == oapp.UpdateAt {
|
|
||||||
t.Fatal("UpdateAt should have updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedApp.ClientSecret != oapp.ClientSecret {
|
|
||||||
t.Fatal("ClientSecret should have not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedApp.Name != oapp.Name {
|
|
||||||
t.Fatal("Name should have updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedApp.Description != oapp.Description {
|
|
||||||
t.Fatal("Description should have updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedApp.IconURL != oapp.IconURL {
|
|
||||||
t.Fatal("IconURL should have updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(updatedApp.CallbackUrls) == len(oapp.CallbackUrls) {
|
if len(updatedApp.CallbackUrls) == len(oapp.CallbackUrls) {
|
||||||
for i, callbackUrl := range updatedApp.CallbackUrls {
|
for i, callbackUrl := range updatedApp.CallbackUrls {
|
||||||
if callbackUrl != oapp.CallbackUrls[i] {
|
assert.Equal(t, oapp.CallbackUrls[i], callbackUrl, "Description should have updated")
|
||||||
t.Fatal("Description should have updated")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, oapp.Homepage, updatedApp.Homepage, "Homepage should have updated")
|
||||||
if updatedApp.Homepage != oapp.Homepage {
|
assert.Equal(t, oapp.IsTrusted, updatedApp.IsTrusted, "IsTrusted should have updated")
|
||||||
t.Fatal("Homepage should have updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedApp.IsTrusted != oapp.IsTrusted {
|
|
||||||
t.Fatal("IsTrusted should have updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
th.LoginBasic2()
|
th.LoginBasic2()
|
||||||
updatedApp.CreatorId = th.BasicUser2.Id
|
updatedApp.CreatorId = th.BasicUser2.Id
|
||||||
@@ -241,24 +195,16 @@ func TestGetOAuthApps(t *testing.T) {
|
|||||||
found2 = true
|
found2 = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
assert.Truef(t, found1, "missing oauth app %v", rapp.Id)
|
||||||
if !found1 || !found2 {
|
assert.Truef(t, found2, "missing oauth app %v", rapp2.Id)
|
||||||
t.Fatal("missing oauth app")
|
|
||||||
}
|
|
||||||
|
|
||||||
apps, resp = AdminClient.GetOAuthApps(1, 1)
|
apps, resp = AdminClient.GetOAuthApps(1, 1)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
require.Equal(t, 1, len(apps), "paging failed")
|
||||||
if len(apps) != 1 {
|
|
||||||
t.Fatal("paging failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
apps, resp = Client.GetOAuthApps(0, 1000)
|
apps, resp = Client.GetOAuthApps(0, 1000)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
require.True(t, len(apps) == 1 || apps[0].Id == rapp2.Id, "wrong apps returned")
|
||||||
if len(apps) != 1 && apps[0].Id != rapp2.Id {
|
|
||||||
t.Fatal("wrong apps returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Revoke permission from regular users.
|
// Revoke permission from regular users.
|
||||||
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||||
@@ -304,25 +250,13 @@ func TestGetOAuthApp(t *testing.T) {
|
|||||||
|
|
||||||
rrapp, resp := AdminClient.GetOAuthApp(rapp.Id)
|
rrapp, resp := AdminClient.GetOAuthApp(rapp.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, rapp.Id, rrapp.Id, "wrong app")
|
||||||
if rapp.Id != rrapp.Id {
|
assert.NotEqual(t, "", rrapp.ClientSecret, "should not be sanitized")
|
||||||
t.Fatal("wrong app")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rrapp.ClientSecret == "" {
|
|
||||||
t.Fatal("should not be sanitized")
|
|
||||||
}
|
|
||||||
|
|
||||||
rrapp2, resp := AdminClient.GetOAuthApp(rapp2.Id)
|
rrapp2, resp := AdminClient.GetOAuthApp(rapp2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app")
|
||||||
if rapp2.Id != rrapp2.Id {
|
assert.NotEqual(t, "", rrapp2.ClientSecret, "should not be sanitized")
|
||||||
t.Fatal("wrong app")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rrapp2.ClientSecret == "" {
|
|
||||||
t.Fatal("should not be sanitized")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.GetOAuthApp(rapp2.Id)
|
_, resp = Client.GetOAuthApp(rapp2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -380,25 +314,13 @@ func TestGetOAuthAppInfo(t *testing.T) {
|
|||||||
|
|
||||||
rrapp, resp := AdminClient.GetOAuthAppInfo(rapp.Id)
|
rrapp, resp := AdminClient.GetOAuthAppInfo(rapp.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, rapp.Id, rrapp.Id, "wrong app")
|
||||||
if rapp.Id != rrapp.Id {
|
assert.Equal(t, "", rrapp.ClientSecret, "should be sanitized")
|
||||||
t.Fatal("wrong app")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rrapp.ClientSecret != "" {
|
|
||||||
t.Fatal("should be sanitized")
|
|
||||||
}
|
|
||||||
|
|
||||||
rrapp2, resp := AdminClient.GetOAuthAppInfo(rapp2.Id)
|
rrapp2, resp := AdminClient.GetOAuthAppInfo(rapp2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app")
|
||||||
if rapp2.Id != rrapp2.Id {
|
assert.Equal(t, "", rrapp2.ClientSecret, "should be sanitized")
|
||||||
t.Fatal("wrong app")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rrapp2.ClientSecret != "" {
|
|
||||||
t.Fatal("should be sanitized")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.GetOAuthAppInfo(rapp2.Id)
|
_, resp = Client.GetOAuthAppInfo(rapp2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -456,10 +378,7 @@ func TestDeleteOAuthApp(t *testing.T) {
|
|||||||
|
|
||||||
pass, resp := AdminClient.DeleteOAuthApp(rapp.Id)
|
pass, resp := AdminClient.DeleteOAuthApp(rapp.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.True(t, pass, "should have passed")
|
||||||
if !pass {
|
|
||||||
t.Fatal("should have passed")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = AdminClient.DeleteOAuthApp(rapp2.Id)
|
_, resp = AdminClient.DeleteOAuthApp(rapp2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -526,14 +445,8 @@ func TestRegenerateOAuthAppSecret(t *testing.T) {
|
|||||||
|
|
||||||
rrapp, resp := AdminClient.RegenerateOAuthAppSecret(rapp.Id)
|
rrapp, resp := AdminClient.RegenerateOAuthAppSecret(rapp.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
assert.Equal(t, rrapp.Id, rapp.Id, "wrong app")
|
||||||
if rrapp.Id != rapp.Id {
|
assert.NotEqual(t, rapp.ClientSecret, rrapp.ClientSecret, "secret didn't change")
|
||||||
t.Fatal("wrong app")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rrapp.ClientSecret == rapp.ClientSecret {
|
|
||||||
t.Fatal("secret didn't change")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = AdminClient.RegenerateOAuthAppSecret(rapp2.Id)
|
_, resp = AdminClient.RegenerateOAuthAppSecret(rapp2.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -608,15 +521,9 @@ func TestGetAuthorizedOAuthAppsForUser(t *testing.T) {
|
|||||||
if a.Id == rapp.Id {
|
if a.Id == rapp.Id {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
|
assert.Equal(t, "", a.ClientSecret, "not sanitized")
|
||||||
if a.ClientSecret != "" {
|
|
||||||
t.Fatal("not sanitized")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !found {
|
|
||||||
t.Fatal("missing app")
|
|
||||||
}
|
}
|
||||||
|
require.True(t, found, "missing app")
|
||||||
|
|
||||||
_, resp = Client.GetAuthorizedOAuthAppsForUser(th.BasicUser2.Id, 0, 1000)
|
_, resp = Client.GetAuthorizedOAuthAppsForUser(th.BasicUser2.Id, 0, 1000)
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
|
|||||||
@@ -7,10 +7,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,19 +61,12 @@ func TestGetOpenGraphMetadata(t *testing.T) {
|
|||||||
|
|
||||||
openGraph, resp := Client.OpenGraph(ts.URL + data["path"].(string))
|
openGraph, resp := Client.OpenGraph(ts.URL + data["path"].(string))
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if strings.Compare(openGraph["title"], data["title"].(string)) != 0 {
|
|
||||||
t.Fatal(fmt.Sprintf(
|
|
||||||
"OG data title mismatch for path \"%s\". Expected title: \"%s\". Actual title: \"%s\"",
|
|
||||||
data["path"].(string), data["title"].(string), openGraph["title"],
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
if ogDataCacheMissCount != data["cacheMissCount"].(int) {
|
require.Equalf(t, openGraph["title"], data["title"].(string),
|
||||||
t.Fatal(fmt.Sprintf(
|
"OG data title mismatch for path \"%s\".")
|
||||||
"Cache miss count didn't match. Expected value %d. Actual value %d.",
|
|
||||||
data["cacheMissCount"].(int), ogDataCacheMissCount,
|
require.Equal(t, ogDataCacheMissCount, data["cacheMissCount"].(int),
|
||||||
))
|
"Cache miss count didn't match.")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableLinkPreviews = false })
|
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableLinkPreviews = false })
|
||||||
|
|||||||
@@ -1399,7 +1399,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
c.LogAuditWithUserId(user.Id, "authenticated")
|
c.LogAuditWithUserId(user.Id, "authenticated")
|
||||||
|
|
||||||
session, err := c.App.DoLogin(w, r, user, deviceId)
|
err = c.App.DoLogin(w, r, user, deviceId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
@@ -1408,7 +1408,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
c.LogAuditWithUserId(user.Id, "success")
|
c.LogAuditWithUserId(user.Id, "success")
|
||||||
|
|
||||||
if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML {
|
if r.Header.Get(model.HEADER_REQUESTED_WITH) == model.HEADER_REQUESTED_WITH_XML {
|
||||||
c.App.AttachSessionCookies(w, r, session)
|
c.App.AttachSessionCookies(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
userTermsOfService, err := c.App.GetUserTermsOfService(user.Id)
|
userTermsOfService, err := c.App.GetUserTermsOfService(user.Id)
|
||||||
|
|||||||
@@ -33,17 +33,9 @@ func TestCreateIncomingWebhook(t *testing.T) {
|
|||||||
rhook, resp := th.SystemAdminClient.CreateIncomingWebhook(hook)
|
rhook, resp := th.SystemAdminClient.CreateIncomingWebhook(hook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if rhook.ChannelId != hook.ChannelId {
|
require.Equal(t, hook.ChannelId, rhook.ChannelId, "channel ids didn't match")
|
||||||
t.Fatal("channel ids didn't match")
|
require.Equal(t, th.SystemAdminUser.Id, rhook.UserId, "user ids didn't match")
|
||||||
}
|
require.Equal(t, th.BasicTeam.Id, rhook.TeamId, "team ids didn't match")
|
||||||
|
|
||||||
if rhook.UserId != th.SystemAdminUser.Id {
|
|
||||||
t.Fatal("user ids didn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
if rhook.TeamId != th.BasicTeam.Id {
|
|
||||||
t.Fatal("team ids didn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
hook.ChannelId = "junk"
|
hook.ChannelId = "junk"
|
||||||
_, resp = th.SystemAdminClient.CreateIncomingWebhook(hook)
|
_, resp = th.SystemAdminClient.CreateIncomingWebhook(hook)
|
||||||
@@ -136,16 +128,12 @@ func TestGetIncomingWebhooks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
require.True(t, found, "missing hook")
|
||||||
t.Fatal("missing hook")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetIncomingWebhooks(0, 1, "")
|
hooks, resp = th.SystemAdminClient.GetIncomingWebhooks(0, 1, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(hooks) != 1 {
|
require.Len(t, hooks, 1, "should only be 1 hook")
|
||||||
t.Fatal("should only be 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "")
|
hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -157,16 +145,12 @@ func TestGetIncomingWebhooks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
require.True(t, found, "missing hook")
|
||||||
t.Fatal("missing hook")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(model.NewId(), 0, 1000, "")
|
hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(model.NewId(), 0, 1000, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(hooks) != 0 {
|
require.Len(t, hooks, 0, "no hooks should be returned")
|
||||||
t.Fatal("no hooks should be returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.GetIncomingWebhooks(0, 1000, "")
|
_, resp = Client.GetIncomingWebhooks(0, 1000, "")
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
@@ -335,11 +319,10 @@ func TestDeleteIncomingWebhook(t *testing.T) {
|
|||||||
rhook, resp = Client.CreateIncomingWebhook(hook)
|
rhook, resp = Client.CreateIncomingWebhook(hook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if status, resp = Client.DeleteIncomingWebhook(rhook.Id); !status {
|
status, resp = Client.DeleteIncomingWebhook(rhook.Id)
|
||||||
t.Fatal("Delete should have succeeded")
|
require.True(t, status, "Delete should have succeeded")
|
||||||
} else {
|
|
||||||
CheckOKStatus(t, resp)
|
CheckOKStatus(t, resp)
|
||||||
}
|
|
||||||
|
|
||||||
// Get now should not return this deleted hook
|
// Get now should not return this deleted hook
|
||||||
_, resp = Client.GetIncomingWebhook(rhook.Id, "")
|
_, resp = Client.GetIncomingWebhook(rhook.Id, "")
|
||||||
@@ -378,13 +361,9 @@ func TestCreateOutgoingWebhook(t *testing.T) {
|
|||||||
rhook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook)
|
rhook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if rhook.ChannelId != hook.ChannelId {
|
assert.Equal(t, hook.ChannelId, rhook.ChannelId, "channel ids didn't match")
|
||||||
t.Fatal("channel ids didn't match")
|
assert.Equal(t, th.SystemAdminUser.Id, rhook.CreatorId, "user ids didn't match")
|
||||||
} else if rhook.CreatorId != th.SystemAdminUser.Id {
|
assert.Equal(t, th.BasicChannel.TeamId, rhook.TeamId, "team ids didn't match")
|
||||||
t.Fatal("user ids didn't match")
|
|
||||||
} else if rhook.TeamId != th.BasicChannel.TeamId {
|
|
||||||
t.Fatal("team ids didn't match")
|
|
||||||
}
|
|
||||||
|
|
||||||
hook.ChannelId = "junk"
|
hook.ChannelId = "junk"
|
||||||
_, resp = th.SystemAdminClient.CreateOutgoingWebhook(hook)
|
_, resp = th.SystemAdminClient.CreateOutgoingWebhook(hook)
|
||||||
@@ -436,16 +415,12 @@ func TestGetOutgoingWebhooks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
require.True(t, found, "missing hook")
|
||||||
t.Fatal("missing hook")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooks(0, 1, "")
|
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooks(0, 1, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(hooks) != 1 {
|
require.Len(t, hooks, 1, "should only be 1 hook")
|
||||||
t.Fatal("should only be 1")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "")
|
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -457,16 +432,12 @@ func TestGetOutgoingWebhooks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
require.True(t, found, "missing hook")
|
||||||
t.Fatal("missing hook")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(model.NewId(), 0, 1000, "")
|
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(model.NewId(), 0, 1000, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if len(hooks) != 0 {
|
require.Len(t, hooks, 0, "no hooks should be returned")
|
||||||
t.Fatal("no hooks should be returned")
|
|
||||||
}
|
|
||||||
|
|
||||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(th.BasicChannel.Id, 0, 1000, "")
|
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(th.BasicChannel.Id, 0, 1000, "")
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
@@ -478,9 +449,7 @@ func TestGetOutgoingWebhooks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
require.True(t, found, "missing hook")
|
||||||
t.Fatal("missing hook")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(model.NewId(), 0, 1000, "")
|
_, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(model.NewId(), 0, 1000, "")
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
@@ -647,9 +616,8 @@ func TestGetOutgoingWebhook(t *testing.T) {
|
|||||||
|
|
||||||
getHook, resp := th.SystemAdminClient.GetOutgoingWebhook(rhook.Id)
|
getHook, resp := th.SystemAdminClient.GetOutgoingWebhook(rhook.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if getHook.Id != rhook.Id {
|
|
||||||
t.Fatal("failed to retrieve the correct outgoing hook")
|
require.Equal(t, getHook.Id, rhook.Id, "failed to retrieve the correct outgoing hook")
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.GetOutgoingWebhook(rhook.Id)
|
_, resp = Client.GetOutgoingWebhook(rhook.Id)
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
@@ -694,29 +662,13 @@ func TestUpdateIncomingHook(t *testing.T) {
|
|||||||
|
|
||||||
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if updatedHook != nil {
|
|
||||||
if updatedHook.DisplayName != "hook2" {
|
|
||||||
t.Fatal("Hook name is not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedHook.Description != "description" {
|
require.NotNil(t, updatedHook, "should not be nil")
|
||||||
t.Fatal("Hook description is not updated")
|
require.Exactly(t, "hook2", updatedHook.DisplayName, "Hook name is not updated")
|
||||||
}
|
require.Exactly(t, "description", updatedHook.Description, "Hook description is not updated")
|
||||||
|
require.Equal(t, updatedHook.ChannelId, th.BasicChannel2.Id, "Hook channel is not updated")
|
||||||
if updatedHook.ChannelId != th.BasicChannel2.Id {
|
require.Empty(t, updatedHook.Username, "Hook username was incorrectly updated")
|
||||||
t.Fatal("Hook channel is not updated")
|
require.Empty(t, updatedHook.IconURL, "Hook icon was incorrectly updated")
|
||||||
}
|
|
||||||
|
|
||||||
if updatedHook.Username != "" {
|
|
||||||
t.Fatal("Hook username was incorrectly updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedHook.IconURL != "" {
|
|
||||||
t.Fatal("Hook icon was incorrectly updated")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Fatal("should not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
//updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id)
|
//updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id)
|
||||||
assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId)
|
assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId)
|
||||||
@@ -734,29 +686,13 @@ func TestUpdateIncomingHook(t *testing.T) {
|
|||||||
|
|
||||||
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if updatedHook != nil {
|
|
||||||
if updatedHook.DisplayName != "hook2" {
|
|
||||||
t.Fatal("Hook name is not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedHook.Description != "description" {
|
require.NotNil(t, updatedHook, "should not be nil")
|
||||||
t.Fatal("Hook description is not updated")
|
require.Exactly(t, "hook2", updatedHook.DisplayName, "Hook name is not updated")
|
||||||
}
|
require.Exactly(t, "description", updatedHook.Description, "Hook description is not updated")
|
||||||
|
require.Equal(t, updatedHook.ChannelId, th.BasicChannel2.Id, "Hook channel is not updated")
|
||||||
if updatedHook.ChannelId != th.BasicChannel2.Id {
|
require.Exactly(t, "username", updatedHook.Username, "Hook username is not updated")
|
||||||
t.Fatal("Hook channel is not updated")
|
require.Exactly(t, "icon", updatedHook.IconURL, "Hook icon is not updated")
|
||||||
}
|
|
||||||
|
|
||||||
if updatedHook.Username != "username" {
|
|
||||||
t.Fatal("Hook username is not updated")
|
|
||||||
}
|
|
||||||
|
|
||||||
if updatedHook.IconURL != "icon" {
|
|
||||||
t.Fatal("Hook icon is not updated")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Fatal("should not be nil")
|
|
||||||
}
|
|
||||||
|
|
||||||
//updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id)
|
//updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id)
|
||||||
assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId)
|
assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId)
|
||||||
@@ -781,13 +717,8 @@ func TestUpdateIncomingHook(t *testing.T) {
|
|||||||
|
|
||||||
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if updatedHook != nil {
|
require.NotNil(t, updatedHook, "should not be nil")
|
||||||
if updatedHook.UpdateAt == createdHook.UpdateAt {
|
require.NotEqual(t, createdHook.UpdateAt, updatedHook.UpdateAt, "failed - hook updateAt is not updated")
|
||||||
t.Fatal("failed - hook updateAt is not updated")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
t.Fatal("should not be nil")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdateNonExistentHook", func(t *testing.T) {
|
t.Run("UpdateNonExistentHook", func(t *testing.T) {
|
||||||
@@ -837,9 +768,7 @@ func TestUpdateIncomingHook(t *testing.T) {
|
|||||||
t.Run("UpdateByDifferentUser", func(t *testing.T) {
|
t.Run("UpdateByDifferentUser", func(t *testing.T) {
|
||||||
updatedHook, resp := Client.UpdateIncomingWebhook(createdHook)
|
updatedHook, resp := Client.UpdateIncomingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if updatedHook.UserId == th.BasicUser2.Id {
|
require.NotEqual(t, th.BasicUser2.Id, updatedHook.UserId, "Hook's creator userId is not retained")
|
||||||
t.Fatal("Hook's creator userId is not retained")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("IncomingHooksDisabled", func(t *testing.T) {
|
t.Run("IncomingHooksDisabled", func(t *testing.T) {
|
||||||
@@ -932,9 +861,7 @@ func TestRegenOutgoingHookToken(t *testing.T) {
|
|||||||
|
|
||||||
regenHookToken, resp := th.SystemAdminClient.RegenOutgoingHookToken(rhook.Id)
|
regenHookToken, resp := th.SystemAdminClient.RegenOutgoingHookToken(rhook.Id)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if regenHookToken.Token == rhook.Token {
|
require.NotEqual(t, rhook.Token, regenHookToken.Token, "regen didn't work properly")
|
||||||
t.Fatal("regen didn't work properly")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, resp = Client.RegenOutgoingHookToken(rhook.Id)
|
_, resp = Client.RegenOutgoingHookToken(rhook.Id)
|
||||||
CheckForbiddenStatus(t, resp)
|
CheckForbiddenStatus(t, resp)
|
||||||
@@ -969,12 +896,9 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
|||||||
|
|
||||||
updatedHook, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook)
|
updatedHook, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if updatedHook.DisplayName != "Cats" {
|
|
||||||
t.Fatal("did not update")
|
require.Exactly(t, "Cats", updatedHook.DisplayName, "did not update")
|
||||||
}
|
require.Exactly(t, "Get me some cats", updatedHook.Description, "did not update")
|
||||||
if updatedHook.Description != "Get me some cats" {
|
|
||||||
t.Fatal("did not update")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("OutgoingHooksDisabled", func(t *testing.T) {
|
t.Run("OutgoingHooksDisabled", func(t *testing.T) {
|
||||||
@@ -995,9 +919,7 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
|||||||
updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook2)
|
updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook2)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if updatedHook2.CreateAt != createdHook2.CreateAt {
|
require.Equal(t, createdHook2.CreateAt, updatedHook2.CreateAt, "failed - hook create at should not be changed")
|
||||||
t.Fatal("failed - hook create at should not be changed")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("ModifyUpdateAt", func(t *testing.T) {
|
t.Run("ModifyUpdateAt", func(t *testing.T) {
|
||||||
@@ -1006,9 +928,7 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
|||||||
updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook)
|
updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if updatedHook2.UpdateAt == createdHook.UpdateAt {
|
require.NotEqual(t, createdHook.UpdateAt, updatedHook2.UpdateAt, "failed - hook updateAt is not updated")
|
||||||
t.Fatal("failed - hook updateAt is not updated")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdateNonExistentHook", func(t *testing.T) {
|
t.Run("UpdateNonExistentHook", func(t *testing.T) {
|
||||||
@@ -1048,12 +968,9 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
|||||||
createdHook.DisplayName = "Basic user 2"
|
createdHook.DisplayName = "Basic user 2"
|
||||||
updatedHook, resp := Client.UpdateOutgoingWebhook(createdHook)
|
updatedHook, resp := Client.UpdateOutgoingWebhook(createdHook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
if updatedHook.DisplayName != "Basic user 2" {
|
|
||||||
t.Fatal("should apply the change")
|
require.Exactly(t, "Basic user 2", updatedHook.DisplayName, "should apply the change")
|
||||||
}
|
require.Equal(t, th.SystemAdminUser.Id, updatedHook.CreatorId, "hook creator should not be changed")
|
||||||
if updatedHook.CreatorId != th.SystemAdminUser.Id {
|
|
||||||
t.Fatal("hook creator should not be changed")
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("UpdateToExistingTriggerWordAndCallback", func(t *testing.T) {
|
t.Run("UpdateToExistingTriggerWordAndCallback", func(t *testing.T) {
|
||||||
@@ -1167,11 +1084,10 @@ func TestDeleteOutgoingHook(t *testing.T) {
|
|||||||
rhook, resp = Client.CreateOutgoingWebhook(hook)
|
rhook, resp = Client.CreateOutgoingWebhook(hook)
|
||||||
CheckNoError(t, resp)
|
CheckNoError(t, resp)
|
||||||
|
|
||||||
if status, resp = Client.DeleteOutgoingWebhook(rhook.Id); !status {
|
status, resp = Client.DeleteOutgoingWebhook(rhook.Id)
|
||||||
t.Fatal("Delete should have succeeded")
|
|
||||||
} else {
|
require.True(t, status, "Delete should have succeeded")
|
||||||
CheckOKStatus(t, resp)
|
CheckOKStatus(t, resp)
|
||||||
}
|
|
||||||
|
|
||||||
// Get now should not return this deleted hook
|
// Get now should not return this deleted hook
|
||||||
_, resp = Client.GetIncomingWebhook(rhook.Id, "")
|
_, resp = Client.GetIncomingWebhook(rhook.Id, "")
|
||||||
|
|||||||
@@ -45,6 +45,20 @@ func TestCreateBot(t *testing.T) {
|
|||||||
require.NotNil(t, err)
|
require.NotNil(t, err)
|
||||||
require.Equal(t, "model.bot.is_valid.description.app_error", err.Id)
|
require.Equal(t, "model.bot.is_valid.description.app_error", err.Id)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("username contains . character", func(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
|
||||||
|
bot, err := th.App.CreateBot(&model.Bot{
|
||||||
|
Username: "username.",
|
||||||
|
Description: "a bot",
|
||||||
|
OwnerId: th.BasicUser.Id,
|
||||||
|
})
|
||||||
|
require.NotNil(t, err)
|
||||||
|
require.Nil(t, bot)
|
||||||
|
require.Equal(t, "model.user.is_valid.email.app_error", err.Id)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("create bot", func(t *testing.T) {
|
t.Run("create bot", func(t *testing.T) {
|
||||||
|
|||||||
@@ -143,6 +143,10 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str
|
|||||||
var text string
|
var text string
|
||||||
if err.Id == "api.channel.add_members.user_denied" {
|
if err.Id == "api.channel.add_members.user_denied" {
|
||||||
text = args.T("api.command_invite.group_constrained_user_denied")
|
text = args.T("api.command_invite.group_constrained_user_denied")
|
||||||
|
} else if err.Id == "store.sql_team.get_member.missing.app_error" {
|
||||||
|
text = args.T("api.command_invite.user_not_in_team.app_error", map[string]interface{}{
|
||||||
|
"Username": userProfile.Username,
|
||||||
|
})
|
||||||
} else {
|
} else {
|
||||||
text = args.T("api.command_invite.fail.app_error")
|
text = args.T("api.command_invite.fail.app_error")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ func TestInviteProvider(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "try to add a user which is not part of the team",
|
desc: "try to add a user which is not part of the team",
|
||||||
expected: "api.command_invite.fail.app_error",
|
expected: "api.command_invite.user_not_in_team.app_error",
|
||||||
msg: basicUser4.Username,
|
msg: basicUser4.Username,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -186,6 +186,7 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
|||||||
post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
|
post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "@channel", post.Message)
|
assert.Equal(t, "@channel", post.Message)
|
||||||
|
assert.Equal(t, "true", post.Props["from_webhook"])
|
||||||
|
|
||||||
// Test Slack attachments text conversion.
|
// Test Slack attachments text conversion.
|
||||||
resp.Attachments = []*model.SlackAttachment{
|
resp.Attachments = []*model.SlackAttachment{
|
||||||
@@ -196,7 +197,11 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
|||||||
|
|
||||||
post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
|
post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
|
||||||
assert.Nil(t, err)
|
assert.Nil(t, err)
|
||||||
assert.Equal(t, "@here", resp.Attachments[0].Text)
|
assert.Equal(t, "@channel", post.Message)
|
||||||
|
if assert.Len(t, post.Attachments(), 1) {
|
||||||
|
assert.Equal(t, "@here", post.Attachments()[0].Text)
|
||||||
|
}
|
||||||
|
assert.Equal(t, "true", post.Props["from_webhook"])
|
||||||
|
|
||||||
channel = th.CreatePrivateChannel(th.BasicTeam)
|
channel = th.CreatePrivateChannel(th.BasicTeam)
|
||||||
resp.ChannelId = channel.Id
|
resp.ChannelId = channel.Id
|
||||||
|
|||||||
@@ -334,7 +334,7 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError {
|
|||||||
authData = nil
|
authData = nil
|
||||||
} else {
|
} else {
|
||||||
// If no AuthData or Password is specified, we must generate a password.
|
// If no AuthData or Password is specified, we must generate a password.
|
||||||
password = model.NewId()
|
password = model.GeneratePassword(*a.Config().PasswordSettings.MinimumLength)
|
||||||
authData = nil
|
authData = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -76,6 +76,13 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
|||||||
close(cchan)
|
close(cchan)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
userChan := make(chan store.StoreResult, 1)
|
||||||
|
go func() {
|
||||||
|
user, err := a.Srv.Store.User().Get(upstreamRequest.UserId)
|
||||||
|
userChan <- store.StoreResult{Data: user, Err: err}
|
||||||
|
close(userChan)
|
||||||
|
}()
|
||||||
|
|
||||||
result := <-pchan
|
result := <-pchan
|
||||||
if result.Err != nil {
|
if result.Err != nil {
|
||||||
if cookie == nil {
|
if cookie == nil {
|
||||||
@@ -89,7 +96,14 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
|||||||
return "", model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "postId doesn't match", http.StatusBadRequest)
|
return "", model.NewAppError("DoPostAction", "api.post.do_action.action_integration.app_error", nil, "postId doesn't match", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
channel, err := a.Srv.Store.Channel().Get(cookie.ChannelId, true)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
upstreamRequest.ChannelId = cookie.ChannelId
|
upstreamRequest.ChannelId = cookie.ChannelId
|
||||||
|
upstreamRequest.ChannelName = channel.Name
|
||||||
|
upstreamRequest.TeamId = channel.TeamId
|
||||||
upstreamRequest.Type = cookie.Type
|
upstreamRequest.Type = cookie.Type
|
||||||
upstreamRequest.Context = cookie.Integration.Context
|
upstreamRequest.Context = cookie.Integration.Context
|
||||||
datasource = cookie.DataSource
|
datasource = cookie.DataSource
|
||||||
@@ -112,6 +126,7 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
|||||||
}
|
}
|
||||||
|
|
||||||
upstreamRequest.ChannelId = post.ChannelId
|
upstreamRequest.ChannelId = post.ChannelId
|
||||||
|
upstreamRequest.ChannelName = channel.Name
|
||||||
upstreamRequest.TeamId = channel.TeamId
|
upstreamRequest.TeamId = channel.TeamId
|
||||||
upstreamRequest.Type = action.Type
|
upstreamRequest.Type = action.Type
|
||||||
upstreamRequest.Context = action.Integration.Context
|
upstreamRequest.Context = action.Integration.Context
|
||||||
@@ -140,6 +155,27 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
|||||||
upstreamURL = action.Integration.URL
|
upstreamURL = action.Integration.URL
|
||||||
}
|
}
|
||||||
|
|
||||||
|
teamChan := make(chan store.StoreResult, 1)
|
||||||
|
go func() {
|
||||||
|
team, err := a.Srv.Store.Team().Get(upstreamRequest.TeamId)
|
||||||
|
teamChan <- store.StoreResult{Data: team, Err: err}
|
||||||
|
close(teamChan)
|
||||||
|
}()
|
||||||
|
|
||||||
|
ur := <-userChan
|
||||||
|
if ur.Err != nil {
|
||||||
|
return "", ur.Err
|
||||||
|
}
|
||||||
|
user := ur.Data.(*model.User)
|
||||||
|
upstreamRequest.UserName = user.Username
|
||||||
|
|
||||||
|
tr := <-teamChan
|
||||||
|
if tr.Err != nil {
|
||||||
|
return "", tr.Err
|
||||||
|
}
|
||||||
|
team := tr.Data.(*model.Team)
|
||||||
|
upstreamRequest.TeamName = team.Name
|
||||||
|
|
||||||
if upstreamRequest.Type == model.POST_ACTION_TYPE_SELECT {
|
if upstreamRequest.Type == model.POST_ACTION_TYPE_SELECT {
|
||||||
if selectedOption != "" {
|
if selectedOption != "" {
|
||||||
if upstreamRequest.Context == nil {
|
if upstreamRequest.Context == nil {
|
||||||
|
|||||||
@@ -80,8 +80,11 @@ func TestPostAction(t *testing.T) {
|
|||||||
assert.NotNil(t, request)
|
assert.NotNil(t, request)
|
||||||
|
|
||||||
assert.Equal(t, request.UserId, th.BasicUser.Id)
|
assert.Equal(t, request.UserId, th.BasicUser.Id)
|
||||||
|
assert.Equal(t, request.UserName, th.BasicUser.Username)
|
||||||
assert.Equal(t, request.ChannelId, th.BasicChannel.Id)
|
assert.Equal(t, request.ChannelId, th.BasicChannel.Id)
|
||||||
|
assert.Equal(t, request.ChannelName, th.BasicChannel.Name)
|
||||||
assert.Equal(t, request.TeamId, th.BasicTeam.Id)
|
assert.Equal(t, request.TeamId, th.BasicTeam.Id)
|
||||||
|
assert.Equal(t, request.TeamName, th.BasicTeam.Name)
|
||||||
assert.True(t, len(request.TriggerId) > 0)
|
assert.True(t, len(request.TriggerId) > 0)
|
||||||
if request.Type == model.POST_ACTION_TYPE_SELECT {
|
if request.Type == model.POST_ACTION_TYPE_SELECT {
|
||||||
assert.Equal(t, request.DataSource, "some_source")
|
assert.Equal(t, request.DataSource, "some_source")
|
||||||
|
|||||||
19
app/login.go
19
app/login.go
@@ -110,7 +110,7 @@ func (a *App) GetUserForLogin(id, loginId string) (*model.User, *model.AppError)
|
|||||||
return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
|
return nil, model.NewAppError("GetUserForLogin", "store.sql_user.get_for_login.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) (*model.Session, *model.AppError) {
|
func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User, deviceId string) *model.AppError {
|
||||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||||
var rejectionReason string
|
var rejectionReason string
|
||||||
pluginContext := a.PluginContext()
|
pluginContext := a.PluginContext()
|
||||||
@@ -120,7 +120,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
}, plugin.UserWillLogInId)
|
}, plugin.UserWillLogInId)
|
||||||
|
|
||||||
if rejectionReason != "" {
|
if rejectionReason != "" {
|
||||||
return nil, model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
|
return model.NewAppError("DoLogin", "Login rejected by plugin: "+rejectionReason, nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
// A special case where we logout of all other sessions with the same Id
|
// A special case where we logout of all other sessions with the same Id
|
||||||
if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
||||||
err.StatusCode = http.StatusInternalServerError
|
err.StatusCode = http.StatusInternalServerError
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays)
|
session.SetExpireInDays(*a.Config().ServiceSettings.SessionLengthWebInDays)
|
||||||
@@ -158,10 +158,11 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
var err *model.AppError
|
var err *model.AppError
|
||||||
if session, err = a.CreateSession(session); err != nil {
|
if session, err = a.CreateSession(session); err != nil {
|
||||||
err.StatusCode = http.StatusInternalServerError
|
err.StatusCode = http.StatusInternalServerError
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||||
|
|
||||||
a.Session = *session
|
a.Session = *session
|
||||||
|
|
||||||
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
if pluginsEnvironment := a.GetPluginsEnvironment(); pluginsEnvironment != nil {
|
||||||
@@ -174,10 +175,10 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return session, nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, session *model.Session) {
|
func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request) {
|
||||||
secure := false
|
secure := false
|
||||||
if GetProtocol(r) == "https" {
|
if GetProtocol(r) == "https" {
|
||||||
secure = true
|
secure = true
|
||||||
@@ -190,7 +191,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
|||||||
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
|
expiresAt := time.Unix(model.GetMillis()/1000+int64(maxAge), 0)
|
||||||
sessionCookie := &http.Cookie{
|
sessionCookie := &http.Cookie{
|
||||||
Name: model.SESSION_COOKIE_TOKEN,
|
Name: model.SESSION_COOKIE_TOKEN,
|
||||||
Value: session.Token,
|
Value: a.Session.Token,
|
||||||
Path: subpath,
|
Path: subpath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
@@ -201,7 +202,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
|||||||
|
|
||||||
userCookie := &http.Cookie{
|
userCookie := &http.Cookie{
|
||||||
Name: model.SESSION_COOKIE_USER,
|
Name: model.SESSION_COOKIE_USER,
|
||||||
Value: session.UserId,
|
Value: a.Session.UserId,
|
||||||
Path: subpath,
|
Path: subpath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
@@ -211,7 +212,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
|||||||
|
|
||||||
csrfCookie := &http.Cookie{
|
csrfCookie := &http.Cookie{
|
||||||
Name: model.SESSION_COOKIE_CSRF,
|
Name: model.SESSION_COOKIE_CSRF,
|
||||||
Value: session.GetCSRF(),
|
Value: a.Session.GetCSRF(),
|
||||||
Path: subpath,
|
Path: subpath,
|
||||||
MaxAge: maxAge,
|
MaxAge: maxAge,
|
||||||
Expires: expiresAt,
|
Expires: expiresAt,
|
||||||
|
|||||||
@@ -1440,6 +1440,17 @@ func TestPluginAPIGetUnsanitizedConfig(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPluginCallLogAPI(t *testing.T) {
|
||||||
|
th := Setup(t).InitBasic()
|
||||||
|
defer th.TearDown()
|
||||||
|
pluginID := "com.mattermost.sample"
|
||||||
|
path, _ := fileutils.FindDir("mattermost-server/app/plugin_api_test")
|
||||||
|
pluginCode, err := ioutil.ReadFile(filepath.Join(path, "plugin_using_log_api.go"))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
setupPluginApiTest(t, string(pluginCode),
|
||||||
|
`{"id": "com.mattermost.sample", "server": {"executable": "backend.exe"}, "settings_schema": {"settings": []}}`, pluginID, th.App)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPluginAddUserToChannel(t *testing.T) {
|
func TestPluginAddUserToChannel(t *testing.T) {
|
||||||
th := Setup(t).InitBasic()
|
th := Setup(t).InitBasic()
|
||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|||||||
29
app/plugin_api_test/plugin_using_log_api.go
Обычный файл
29
app/plugin_api_test/plugin_using_log_api.go
Обычный файл
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/mattermost/mattermost-server/plugin"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PluginUsingLogAPI struct {
|
||||||
|
plugin.MattermostPlugin
|
||||||
|
}
|
||||||
|
|
||||||
|
type Foo struct {
|
||||||
|
bar float64
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
plugin.ClientMain(&PluginUsingLogAPI{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *PluginUsingLogAPI) OnActivate() error {
|
||||||
|
p.API.LogDebug("LogDebug", "one", 1, "two", "two", "foo", Foo{bar: 3.1416})
|
||||||
|
p.API.LogInfo("LogInfo", "one", 1, "two", "two", "foo", Foo{bar: 3.1416})
|
||||||
|
p.API.LogWarn("LogWarn", "one", 1, "two", "two", "foo", Foo{bar: 3.1416})
|
||||||
|
p.API.LogError("LogError", "error", errors.WithStack(errors.New("boom!")))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -694,7 +694,7 @@ func TestUserWillLogIn_Blocked(t *testing.T) {
|
|||||||
|
|
||||||
r := &http.Request{}
|
r := &http.Request{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
_, err = th.App.DoLogin(w, r, th.BasicUser, "")
|
err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||||
|
|
||||||
assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
|
assert.Contains(t, err.Id, "Login rejected by plugin", "Expected Login rejected by plugin, got %s", err.Id)
|
||||||
}
|
}
|
||||||
@@ -733,10 +733,10 @@ func TestUserWillLogInIn_Passed(t *testing.T) {
|
|||||||
|
|
||||||
r := &http.Request{}
|
r := &http.Request{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
session, err := th.App.DoLogin(w, r, th.BasicUser, "")
|
err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||||
|
|
||||||
assert.Nil(t, err, "Expected nil, got %s", err)
|
assert.Nil(t, err, "Expected nil, got %s", err)
|
||||||
assert.Equal(t, session.UserId, th.BasicUser.Id)
|
assert.Equal(t, th.App.Session.UserId, th.BasicUser.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUserHasLoggedIn(t *testing.T) {
|
func TestUserHasLoggedIn(t *testing.T) {
|
||||||
@@ -774,7 +774,7 @@ func TestUserHasLoggedIn(t *testing.T) {
|
|||||||
|
|
||||||
r := &http.Request{}
|
r := &http.Request{}
|
||||||
w := httptest.NewRecorder()
|
w := httptest.NewRecorder()
|
||||||
_, err = th.App.DoLogin(w, r, th.BasicUser, "")
|
err = th.App.DoLogin(w, r, th.BasicUser, "")
|
||||||
|
|
||||||
assert.Nil(t, err, "Expected nil, got %s", err)
|
assert.Nil(t, err, "Expected nil, got %s", err)
|
||||||
|
|
||||||
|
|||||||
@@ -52,7 +52,3 @@ services:
|
|||||||
http.host: "0.0.0.0"
|
http.host: "0.0.0.0"
|
||||||
transport.host: "127.0.0.1"
|
transport.host: "127.0.0.1"
|
||||||
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
||||||
redis:
|
|
||||||
image: redis
|
|
||||||
networks:
|
|
||||||
- mm-test
|
|
||||||
|
|||||||
@@ -28,10 +28,6 @@ services:
|
|||||||
extends:
|
extends:
|
||||||
file: docker-compose.common.yml
|
file: docker-compose.common.yml
|
||||||
service: elasticsearch
|
service: elasticsearch
|
||||||
redis:
|
|
||||||
extends:
|
|
||||||
file: docker-compose.common.yml
|
|
||||||
service: redis
|
|
||||||
|
|
||||||
start_dependencies:
|
start_dependencies:
|
||||||
image: mattermost/mattermost-wait-for-dep:latest
|
image: mattermost/mattermost-wait-for-dep:latest
|
||||||
@@ -44,8 +40,7 @@ services:
|
|||||||
- inbucket
|
- inbucket
|
||||||
- openldap
|
- openldap
|
||||||
- elasticsearch
|
- elasticsearch
|
||||||
- redis
|
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200
|
||||||
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 redis:6379
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
mm-test:
|
mm-test:
|
||||||
|
|||||||
@@ -55,9 +55,3 @@ clean-old-docker:
|
|||||||
docker stop mattermost-elasticsearch > /dev/null; \
|
docker stop mattermost-elasticsearch > /dev/null; \
|
||||||
docker rm -v mattermost-elasticsearch > /dev/null; \
|
docker rm -v mattermost-elasticsearch > /dev/null; \
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@if [ $(shell docker ps -a | grep -ci mattermost-redis) -eq 1 ]; then \
|
|
||||||
echo removing mattermost-redis; \
|
|
||||||
docker stop mattermost-redis > /dev/null; \
|
|
||||||
docker rm -v mattermost-redis > /dev/null; \
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -46,13 +46,6 @@ services:
|
|||||||
extends:
|
extends:
|
||||||
file: build/docker-compose.common.yml
|
file: build/docker-compose.common.yml
|
||||||
service: elasticsearch
|
service: elasticsearch
|
||||||
redis:
|
|
||||||
container_name: mattermost-redis
|
|
||||||
ports:
|
|
||||||
- "6379:6379"
|
|
||||||
extends:
|
|
||||||
file: build/docker-compose.common.yml
|
|
||||||
service: redis
|
|
||||||
start_dependencies:
|
start_dependencies:
|
||||||
image: mattermost/mattermost-wait-for-dep:latest
|
image: mattermost/mattermost-wait-for-dep:latest
|
||||||
networks:
|
networks:
|
||||||
@@ -64,8 +57,7 @@ services:
|
|||||||
- inbucket
|
- inbucket
|
||||||
- openldap
|
- openldap
|
||||||
- elasticsearch
|
- elasticsearch
|
||||||
- redis
|
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200
|
||||||
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 redis:6379
|
|
||||||
|
|
||||||
networks:
|
networks:
|
||||||
mm-test:
|
mm-test:
|
||||||
|
|||||||
1
go.mod
1
go.mod
@@ -16,7 +16,6 @@ require (
|
|||||||
github.com/fortytw2/leaktest v1.3.0 // indirect
|
github.com/fortytw2/leaktest v1.3.0 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.4.7
|
github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/go-gorp/gorp v2.0.0+incompatible // indirect
|
github.com/go-gorp/gorp v2.0.0+incompatible // indirect
|
||||||
github.com/go-redis/redis v6.15.5+incompatible
|
|
||||||
github.com/go-sql-driver/mysql v1.4.1
|
github.com/go-sql-driver/mysql v1.4.1
|
||||||
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
github.com/google/uuid v1.1.1 // indirect
|
github.com/google/uuid v1.1.1 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -94,8 +94,6 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
|
|||||||
github.com/go-kit/kit v0.9.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=
|
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||||
github.com/go-redis/redis v6.15.5+incompatible h1:pLky8I0rgiblWfa8C1EV7fPEUv0aH6vKRaYHc/YRHVk=
|
|
||||||
github.com/go-redis/redis v6.15.5+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
|
|
||||||
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
github.com/go-sql-driver/mysql v1.4.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||||
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.4.1/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
|
|||||||
@@ -798,6 +798,10 @@
|
|||||||
"id": "api.command_invite.user_already_in_channel.app_error",
|
"id": "api.command_invite.user_already_in_channel.app_error",
|
||||||
"translation": "{{.User}} is already in the channel."
|
"translation": "{{.User}} is already in the channel."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "api.command_invite.user_not_in_team.app_error",
|
||||||
|
"translation": "@{{.Username}} is not a member of the team."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "api.command_invite_people.permission.app_error",
|
"id": "api.command_invite_people.permission.app_error",
|
||||||
"translation": "You don't have permission to invite new users to this server."
|
"translation": "You don't have permission to invite new users to this server."
|
||||||
|
|||||||
@@ -86,11 +86,13 @@ func (schedulers *Schedulers) Start() *Schedulers {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for {
|
for {
|
||||||
|
timer := time.NewTimer(1 * time.Minute)
|
||||||
select {
|
select {
|
||||||
case <-schedulers.stop:
|
case <-schedulers.stop:
|
||||||
mlog.Debug("Schedulers received stop signal.")
|
mlog.Debug("Schedulers received stop signal.")
|
||||||
|
timer.Stop()
|
||||||
return
|
return
|
||||||
case now = <-time.After(1 * time.Minute):
|
case now = <-timer.C:
|
||||||
cfg := schedulers.jobs.Config()
|
cfg := schedulers.jobs.Config()
|
||||||
|
|
||||||
for idx, nextTime := range schedulers.nextRunTimes {
|
for idx, nextTime := range schedulers.nextRunTimes {
|
||||||
@@ -128,6 +130,7 @@ func (schedulers *Schedulers) Start() *Schedulers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
timer.Stop()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}()
|
}()
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -167,7 +166,7 @@ func UserFromBot(b *Bot) *User {
|
|||||||
return &User{
|
return &User{
|
||||||
Id: b.UserId,
|
Id: b.UserId,
|
||||||
Username: b.Username,
|
Username: b.Username,
|
||||||
Email: fmt.Sprintf("%s@localhost", strings.ToLower(b.Username)),
|
Email: NormalizeEmail(fmt.Sprintf("%s@localhost", b.Username)),
|
||||||
FirstName: b.DisplayName,
|
FirstName: b.DisplayName,
|
||||||
Roles: SYSTEM_USER_ROLE_ID,
|
Roles: SYSTEM_USER_ROLE_ID,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -157,14 +157,17 @@ type PostActionIntegration struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type PostActionIntegrationRequest struct {
|
type PostActionIntegrationRequest struct {
|
||||||
UserId string `json:"user_id"`
|
UserId string `json:"user_id"`
|
||||||
ChannelId string `json:"channel_id"`
|
UserName string `json:"user_name"`
|
||||||
TeamId string `json:"team_id"`
|
ChannelId string `json:"channel_id"`
|
||||||
PostId string `json:"post_id"`
|
ChannelName string `json:"channel_name"`
|
||||||
TriggerId string `json:"trigger_id"`
|
TeamId string `json:"team_id"`
|
||||||
Type string `json:"type"`
|
TeamName string `json:"team_domain"`
|
||||||
DataSource string `json:"data_source"`
|
PostId string `json:"post_id"`
|
||||||
Context map[string]interface{} `json:"context,omitempty"`
|
TriggerId string `json:"trigger_id"`
|
||||||
|
Type string `json:"type"`
|
||||||
|
DataSource string `json:"data_source"`
|
||||||
|
Context map[string]interface{} `json:"context,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type PostActionIntegrationResponse struct {
|
type PostActionIntegrationResponse struct {
|
||||||
|
|||||||
@@ -9,10 +9,12 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/services/timezones"
|
"github.com/mattermost/mattermost-server/services/timezones"
|
||||||
@@ -851,3 +853,27 @@ func UsersWithGroupsAndCountFromJson(data io.Reader) *UsersWithGroupsAndCount {
|
|||||||
json.Unmarshal(bodyBytes, uwg)
|
json.Unmarshal(bodyBytes, uwg)
|
||||||
return uwg
|
return uwg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var passwordRandomSource = rand.NewSource(time.Now().Unix())
|
||||||
|
var passwordSpecialChars = "!$%^&*(),."
|
||||||
|
var passwordNumbers = "0123456789"
|
||||||
|
var passwordUpperCaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||||
|
var passwordLowerCaseLetters = "abcdefghijklmnopqrstuvwxyz"
|
||||||
|
var passwordAllChars = passwordSpecialChars + passwordNumbers + passwordUpperCaseLetters + passwordLowerCaseLetters
|
||||||
|
|
||||||
|
func GeneratePassword(minimumLength int) string {
|
||||||
|
r := rand.New(passwordRandomSource)
|
||||||
|
|
||||||
|
// Make sure we are guaranteed at least one of each type to meet any possible password complexity requirements.
|
||||||
|
password := string([]rune(passwordUpperCaseLetters)[r.Intn(len(passwordUpperCaseLetters))]) +
|
||||||
|
string([]rune(passwordNumbers)[r.Intn(len(passwordNumbers))]) +
|
||||||
|
string([]rune(passwordLowerCaseLetters)[r.Intn(len(passwordLowerCaseLetters))]) +
|
||||||
|
string([]rune(passwordSpecialChars)[r.Intn(len(passwordSpecialChars))])
|
||||||
|
|
||||||
|
for len(password) < minimumLength {
|
||||||
|
i := r.Intn(len(passwordAllChars))
|
||||||
|
password = password + string([]rune(passwordAllChars)[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return password
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@@ -350,3 +351,25 @@ func TestUserSlice(t *testing.T) {
|
|||||||
assert.Equal(t, 1, len(nonBotUsers))
|
assert.Equal(t, 1, len(nonBotUsers))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGeneratePassword(t *testing.T) {
|
||||||
|
passwordRandomSource = rand.NewSource(12345)
|
||||||
|
|
||||||
|
t.Run("Should be the minimum length or 4, whichever is less", func(t *testing.T) {
|
||||||
|
password1 := GeneratePassword(5)
|
||||||
|
assert.Len(t, password1, 5)
|
||||||
|
password2 := GeneratePassword(10)
|
||||||
|
assert.Len(t, password2, 10)
|
||||||
|
password3 := GeneratePassword(1)
|
||||||
|
assert.Len(t, password3, 4)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Should contain at least one of symbols, upper case, lower case and numbers", func(t *testing.T) {
|
||||||
|
password := GeneratePassword(4)
|
||||||
|
require.Len(t, password, 4)
|
||||||
|
assert.Contains(t, []rune(passwordUpperCaseLetters), []rune(password)[0])
|
||||||
|
assert.Contains(t, []rune(passwordNumbers), []rune(password)[1])
|
||||||
|
assert.Contains(t, []rune(passwordLowerCaseLetters), []rune(password)[2])
|
||||||
|
assert.Contains(t, []rune(passwordSpecialChars), []rune(password)[3])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
@@ -663,7 +663,6 @@ type API interface {
|
|||||||
// LogDebug writes a log message to the Mattermost server log file.
|
// LogDebug writes a log message to the Mattermost server log file.
|
||||||
// Appropriate context such as the plugin name will already be added as fields so plugins
|
// Appropriate context such as the plugin name will already be added as fields so plugins
|
||||||
// do not need to add that info.
|
// do not need to add that info.
|
||||||
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
|
|
||||||
//
|
//
|
||||||
// Minimum server version: 5.2
|
// Minimum server version: 5.2
|
||||||
LogDebug(msg string, keyValuePairs ...interface{})
|
LogDebug(msg string, keyValuePairs ...interface{})
|
||||||
@@ -671,7 +670,6 @@ type API interface {
|
|||||||
// LogInfo writes a log message to the Mattermost server log file.
|
// LogInfo writes a log message to the Mattermost server log file.
|
||||||
// Appropriate context such as the plugin name will already be added as fields so plugins
|
// Appropriate context such as the plugin name will already be added as fields so plugins
|
||||||
// do not need to add that info.
|
// do not need to add that info.
|
||||||
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
|
|
||||||
//
|
//
|
||||||
// Minimum server version: 5.2
|
// Minimum server version: 5.2
|
||||||
LogInfo(msg string, keyValuePairs ...interface{})
|
LogInfo(msg string, keyValuePairs ...interface{})
|
||||||
@@ -679,7 +677,6 @@ type API interface {
|
|||||||
// LogError writes a log message to the Mattermost server log file.
|
// LogError writes a log message to the Mattermost server log file.
|
||||||
// Appropriate context such as the plugin name will already be added as fields so plugins
|
// Appropriate context such as the plugin name will already be added as fields so plugins
|
||||||
// do not need to add that info.
|
// do not need to add that info.
|
||||||
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
|
|
||||||
//
|
//
|
||||||
// Minimum server version: 5.2
|
// Minimum server version: 5.2
|
||||||
LogError(msg string, keyValuePairs ...interface{})
|
LogError(msg string, keyValuePairs ...interface{})
|
||||||
@@ -687,7 +684,6 @@ type API interface {
|
|||||||
// LogWarn writes a log message to the Mattermost server log file.
|
// LogWarn writes a log message to the Mattermost server log file.
|
||||||
// Appropriate context such as the plugin name will already be added as fields so plugins
|
// Appropriate context such as the plugin name will already be added as fields so plugins
|
||||||
// do not need to add that info.
|
// do not need to add that info.
|
||||||
// keyValuePairs should be primitive go types or other values that can be encoded by encoding/gob
|
|
||||||
//
|
//
|
||||||
// Minimum server version: 5.2
|
// Minimum server version: 5.2
|
||||||
LogWarn(msg string, keyValuePairs ...interface{})
|
LogWarn(msg string, keyValuePairs ...interface{})
|
||||||
|
|||||||
50
plugin/checker/check_api.go
Обычный файл
50
plugin/checker/check_api.go
Обычный файл
@@ -0,0 +1,50 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/plugin/checker/internal/asthelpers"
|
||||||
|
"github.com/mattermost/mattermost-server/plugin/checker/internal/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkAPIVersionComments(pkgPath string) (result, error) {
|
||||||
|
pkg, err := asthelpers.GetPackage(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiInterface, err := asthelpers.FindInterface("API", pkg.Syntax)
|
||||||
|
if err != nil {
|
||||||
|
return result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidMethods := findInvalidMethods(apiInterface.Methods.List)
|
||||||
|
return result{Errors: renderErrors(pkg.Fset, invalidMethods)}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func findInvalidMethods(methods []*ast.Field) []*ast.Field {
|
||||||
|
var invalid []*ast.Field
|
||||||
|
for _, m := range methods {
|
||||||
|
if !hasValidMinimumVersionComment(m.Doc.Text()) {
|
||||||
|
invalid = append(invalid, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasValidMinimumVersionComment(s string) bool {
|
||||||
|
return version.ExtractMinimumVersionFromComment(s) != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func renderErrors(fset *token.FileSet, methods []*ast.Field) []string {
|
||||||
|
var out []string
|
||||||
|
for _, m := range methods {
|
||||||
|
out = append(out, renderWithFilePosition(fset, m.Pos(), fmt.Sprintf("missing a minimum server version comment on method %s", m.Names[0].Name)))
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
@@ -10,29 +10,32 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRunCheck(t *testing.T) {
|
func TestCheckAPIVersionComments(t *testing.T) {
|
||||||
testCases := []struct {
|
testCases := []struct {
|
||||||
name, pkgPath, err string
|
name, pkgPath, err string
|
||||||
|
expected result
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "valid comments",
|
name: "valid comments",
|
||||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/valid",
|
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/valid",
|
||||||
err: "",
|
err: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "invalid comments",
|
name: "invalid comments",
|
||||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/invalid",
|
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/invalid",
|
||||||
err: "test/invalid/invalid.go:15:2: missing a minimum server version comment\n",
|
expected: result{
|
||||||
|
Errors: []string{"internal/test/invalid/invalid.go:15:2: missing a minimum server version comment on method InvalidMethod"},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "missing API interface",
|
name: "missing API interface",
|
||||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/missing",
|
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/missing",
|
||||||
err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/missing",
|
err: "could not find API interface",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "non-existent package path",
|
name: "non-existent package path",
|
||||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
|
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/does_not_exist",
|
||||||
err: "could not find API interface in package github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
|
err: "could not find API interface",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -43,7 +46,8 @@ func TestRunCheck(t *testing.T) {
|
|||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
err := runCheck(tc.pkgPath)
|
res, err := checkAPIVersionComments(tc.pkgPath)
|
||||||
|
assert.Equal(t, res, tc.expected)
|
||||||
|
|
||||||
if tc.err != "" {
|
if tc.err != "" {
|
||||||
assert.EqualError(t, err, tc.err)
|
assert.EqualError(t, err, tc.err)
|
||||||
137
plugin/checker/check_helpers.go
Обычный файл
137
plugin/checker/check_helpers.go
Обычный файл
@@ -0,0 +1,137 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/ast"
|
||||||
|
"go/token"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/plugin/checker/internal/asthelpers"
|
||||||
|
"github.com/mattermost/mattermost-server/plugin/checker/internal/version"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkHelpersVersionComments(pkgPath string) (result, error) {
|
||||||
|
pkg, err := asthelpers.GetPackage(pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
api, apiIdent, err := asthelpers.FindInterfaceWithIdent("API", pkg.Syntax)
|
||||||
|
if err != nil {
|
||||||
|
return result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiObj := pkg.TypesInfo.ObjectOf(apiIdent)
|
||||||
|
if apiObj == nil {
|
||||||
|
return result{}, errors.New("could not find type object for API interface")
|
||||||
|
}
|
||||||
|
|
||||||
|
helpers, err := asthelpers.FindInterface("Helpers", pkg.Syntax)
|
||||||
|
if err != nil {
|
||||||
|
return result{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
apiVersions := mapMinimumVersionsByMethodName(api.Methods.List)
|
||||||
|
|
||||||
|
helpersPositions := mapPositionsByMethodName(helpers.Methods.List)
|
||||||
|
helpersVersions := mapMinimumVersionsByMethodName(helpers.Methods.List)
|
||||||
|
|
||||||
|
implMethods := asthelpers.FindReceiverMethods("HelpersImpl", pkg.Syntax)
|
||||||
|
implVersions := mapEffectiveVersionByMethod(pkg.TypesInfo, apiObj.Type(), apiVersions, implMethods)
|
||||||
|
|
||||||
|
return validateMethods(pkg.Fset, helpersPositions, helpersVersions, implVersions), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateMethods(
|
||||||
|
fset *token.FileSet,
|
||||||
|
helpersPositions map[string]token.Pos,
|
||||||
|
helpersVersions map[string]version.V,
|
||||||
|
implVersions map[string]version.V,
|
||||||
|
) result {
|
||||||
|
var res result
|
||||||
|
|
||||||
|
for name, helperVer := range helpersVersions {
|
||||||
|
pos := helpersPositions[name]
|
||||||
|
|
||||||
|
implVer, ok := implVersions[name]
|
||||||
|
if !ok {
|
||||||
|
res.Errors = append(res.Errors, renderWithFilePosition(
|
||||||
|
fset,
|
||||||
|
pos,
|
||||||
|
fmt.Sprintf("missing implementation for method %s", name)),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if helperVer == "" {
|
||||||
|
res.Errors = append(res.Errors, renderWithFilePosition(
|
||||||
|
fset,
|
||||||
|
pos,
|
||||||
|
fmt.Sprintf("missing a minimum server version comment on method %s", name)),
|
||||||
|
)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if helperVer == implVer {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if helperVer.LessThan(implVer) {
|
||||||
|
res.Errors = append(res.Errors, renderWithFilePosition(
|
||||||
|
fset,
|
||||||
|
pos,
|
||||||
|
fmt.Sprintf("documented minimum server version too low on method %s", name)),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res.Warnings = append(res.Warnings, renderWithFilePosition(
|
||||||
|
fset,
|
||||||
|
pos,
|
||||||
|
fmt.Sprintf("documented minimum server version too high on method %s", name)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapEffectiveVersionByMethod(info *types.Info, apiType types.Type, versions map[string]version.V, methods []*ast.FuncDecl) map[string]version.V {
|
||||||
|
effectiveVersions := map[string]version.V{}
|
||||||
|
for _, m := range methods {
|
||||||
|
apiMethodsCalled := asthelpers.FindMethodsCalledOnType(info, apiType, m)
|
||||||
|
effectiveVersions[m.Name.Name] = getEffectiveMinimumVersion(versions, apiMethodsCalled)
|
||||||
|
}
|
||||||
|
return effectiveVersions
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapMinimumVersionsByMethodName(methods []*ast.Field) map[string]version.V {
|
||||||
|
versions := map[string]version.V{}
|
||||||
|
for _, m := range methods {
|
||||||
|
versions[m.Names[0].Name] = version.V(version.ExtractMinimumVersionFromComment(m.Doc.Text()))
|
||||||
|
}
|
||||||
|
return versions
|
||||||
|
}
|
||||||
|
|
||||||
|
func mapPositionsByMethodName(methods []*ast.Field) map[string]token.Pos {
|
||||||
|
pos := map[string]token.Pos{}
|
||||||
|
for _, m := range methods {
|
||||||
|
pos[m.Names[0].Name] = m.Pos()
|
||||||
|
}
|
||||||
|
return pos
|
||||||
|
}
|
||||||
|
|
||||||
|
func getEffectiveMinimumVersion(info map[string]version.V, methods []string) version.V {
|
||||||
|
var highest version.V
|
||||||
|
for _, m := range methods {
|
||||||
|
if current, ok := info[m]; ok {
|
||||||
|
if current.GreaterThanOrEqualTo(highest) {
|
||||||
|
highest = current
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return highest
|
||||||
|
}
|
||||||
48
plugin/checker/check_helpers_test.go
Обычный файл
48
plugin/checker/check_helpers_test.go
Обычный файл
@@ -0,0 +1,48 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCheckHelpersVersionComments(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
name, pkgPath string
|
||||||
|
expected result
|
||||||
|
err string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "valid versions",
|
||||||
|
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/valid",
|
||||||
|
expected: result{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid versions",
|
||||||
|
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/invalid",
|
||||||
|
expected: result{
|
||||||
|
Errors: []string{"internal/test/invalid/invalid.go:20:2: documented minimum server version too low on method LowerVersionMethod"},
|
||||||
|
Warnings: []string{"internal/test/invalid/invalid.go:23:2: documented minimum server version too high on method HigherVersionMethod"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
res, err := checkHelpersVersionComments(tc.pkgPath)
|
||||||
|
assert.Equal(tc.expected, res)
|
||||||
|
|
||||||
|
if tc.err != "" {
|
||||||
|
assert.EqualError(err, tc.err)
|
||||||
|
} else {
|
||||||
|
assert.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
129
plugin/checker/internal/asthelpers/helpers.go
Обычный файл
129
plugin/checker/internal/asthelpers/helpers.go
Обычный файл
@@ -0,0 +1,129 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package asthelpers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"go/ast"
|
||||||
|
"go/types"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"golang.org/x/tools/go/packages"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetPackage(pkgPath string) (*packages.Package, error) {
|
||||||
|
cfg := &packages.Config{
|
||||||
|
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax | packages.NeedTypesInfo,
|
||||||
|
}
|
||||||
|
pkgs, err := packages.Load(cfg, pkgPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pkgs) == 0 {
|
||||||
|
return nil, errors.Errorf("could not find package %s", pkgPath)
|
||||||
|
}
|
||||||
|
return pkgs[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindInterface(name string, files []*ast.File) (*ast.InterfaceType, error) {
|
||||||
|
iface, _, err := FindInterfaceWithIdent(name, files)
|
||||||
|
return iface, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindInterfaceWithIdent(name string, files []*ast.File) (*ast.InterfaceType, *ast.Ident, error) {
|
||||||
|
var (
|
||||||
|
ident *ast.Ident
|
||||||
|
iface *ast.InterfaceType
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, f := range files {
|
||||||
|
ast.Inspect(f, func(n ast.Node) bool {
|
||||||
|
if t, ok := n.(*ast.TypeSpec); ok {
|
||||||
|
if iface != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if i, ok := t.Type.(*ast.InterfaceType); ok && t.Name.Name == name {
|
||||||
|
ident = t.Name
|
||||||
|
iface = i
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
if iface != nil {
|
||||||
|
return iface, ident, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil, errors.Errorf("could not find %s interface", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindMethodsCalledOnType(info *types.Info, typ types.Type, caller *ast.FuncDecl) []string {
|
||||||
|
var methods []string
|
||||||
|
|
||||||
|
ast.Inspect(caller, func(n ast.Node) bool {
|
||||||
|
if s, ok := n.(*ast.SelectorExpr); ok {
|
||||||
|
|
||||||
|
var receiver *ast.Ident
|
||||||
|
switch r := s.X.(type) {
|
||||||
|
case *ast.Ident:
|
||||||
|
// Left-hand side of the selector is an identifier, eg:
|
||||||
|
//
|
||||||
|
// a := p.API
|
||||||
|
// a.GetTeams()
|
||||||
|
//
|
||||||
|
receiver = r
|
||||||
|
case *ast.SelectorExpr:
|
||||||
|
// Left-hand side of the selector is a selector, eg:
|
||||||
|
//
|
||||||
|
// p.API.GetTeams()
|
||||||
|
//
|
||||||
|
receiver = r.Sel
|
||||||
|
}
|
||||||
|
|
||||||
|
if receiver != nil {
|
||||||
|
obj := info.ObjectOf(receiver)
|
||||||
|
if obj != nil && types.Identical(obj.Type(), typ) {
|
||||||
|
methods = append(methods, s.Sel.Name)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return methods
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindReceiverMethods(receiverName string, files []*ast.File) []*ast.FuncDecl {
|
||||||
|
var fns []*ast.FuncDecl
|
||||||
|
for _, f := range files {
|
||||||
|
ast.Inspect(f, func(n ast.Node) bool {
|
||||||
|
if fn, ok := n.(*ast.FuncDecl); ok {
|
||||||
|
r := extractReceiverTypeName(fn)
|
||||||
|
if r == receiverName {
|
||||||
|
fns = append(fns, fn)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return fns
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractReceiverTypeName(fn *ast.FuncDecl) string {
|
||||||
|
if fn.Recv != nil {
|
||||||
|
t := fn.Recv.List[0].Type
|
||||||
|
// Unwrap the pointer type (a star expression)
|
||||||
|
if se, ok := t.(*ast.StarExpr); ok {
|
||||||
|
t = se.X
|
||||||
|
}
|
||||||
|
if id, ok := t.(*ast.Ident); ok {
|
||||||
|
return id.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
@@ -14,3 +14,23 @@ type API interface {
|
|||||||
// plugin comment checker with an invalid comment.
|
// plugin comment checker with an invalid comment.
|
||||||
InvalidMethod()
|
InvalidMethod()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Helpers interface {
|
||||||
|
// Minimum server version: 1.1
|
||||||
|
LowerVersionMethod()
|
||||||
|
|
||||||
|
// Minimum server version: 1.3
|
||||||
|
HigherVersionMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelpersImpl struct {
|
||||||
|
api API
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HelpersImpl) LowerVersionMethod() {
|
||||||
|
h.api.ValidMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HelpersImpl) HigherVersionMethod() {
|
||||||
|
h.api.ValidMethod()
|
||||||
|
}
|
||||||
44
plugin/checker/internal/test/valid/valid.go
Обычный файл
44
plugin/checker/internal/test/valid/valid.go
Обычный файл
@@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package valid
|
||||||
|
|
||||||
|
type API interface {
|
||||||
|
// ValidMethod is a fake method for testing the
|
||||||
|
// plugin comment checker with a valid comment.
|
||||||
|
//
|
||||||
|
// Minimum server version: 1.2.3
|
||||||
|
ValidMethod()
|
||||||
|
|
||||||
|
// Minimum server version: 1.5
|
||||||
|
NewerValidMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Helpers interface {
|
||||||
|
// Minimum server version: 1.2.3
|
||||||
|
ValidHelperMethod()
|
||||||
|
|
||||||
|
// Minimum server version: 1.5
|
||||||
|
NewerValidHelperMethod()
|
||||||
|
|
||||||
|
// Minimum server version: 1.5
|
||||||
|
IndirectReferenceMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
type HelpersImpl struct {
|
||||||
|
api API
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HelpersImpl) ValidHelperMethod() {
|
||||||
|
h.api.ValidMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HelpersImpl) NewerValidHelperMethod() {
|
||||||
|
h.api.NewerValidMethod()
|
||||||
|
h.api.ValidMethod()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *HelpersImpl) IndirectReferenceMethod() {
|
||||||
|
a := h.api
|
||||||
|
a.NewerValidMethod()
|
||||||
|
}
|
||||||
22
plugin/checker/internal/version/comments.go
Обычный файл
22
plugin/checker/internal/version/comments.go
Обычный файл
@@ -0,0 +1,22 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var versionCommentRE = regexp.MustCompile(`^Minimum server version: (\d+\.\d+(?:\.\d+[\w-]*)?)$`)
|
||||||
|
|
||||||
|
func ExtractMinimumVersionFromComment(s string) string {
|
||||||
|
lines := strings.Split(strings.TrimSpace(s), "\n")
|
||||||
|
if len(lines) > 0 {
|
||||||
|
lastLine := lines[len(lines)-1]
|
||||||
|
if m := versionCommentRE.FindStringSubmatch(lastLine); len(m) >= 1 {
|
||||||
|
return m[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
49
plugin/checker/internal/version/comments_test.go
Обычный файл
49
plugin/checker/internal/version/comments_test.go
Обычный файл
@@ -0,0 +1,49 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExtractVersionFromComment(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
input: "This is a comment.\n\nMinimum server version: 1.2.3-rc1\n",
|
||||||
|
expected: "1.2.3-rc1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "This is a comment.\n\nMinimum server version: 1.2.3\n",
|
||||||
|
expected: "1.2.3",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "This is a comment.\n\nMinimum server version: 1.2\n",
|
||||||
|
expected: "1.2",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "This is a comment.\n\nMinimum server version: 1\n",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "This is a comment.\n",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "",
|
||||||
|
expected: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("%+v", tc), func(t *testing.T) {
|
||||||
|
assert.Equal(t, tc.expected, ExtractMinimumVersionFromComment(tc.input))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
80
plugin/checker/internal/version/version.go
Обычный файл
80
plugin/checker/internal/version/version.go
Обычный файл
@@ -0,0 +1,80 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type V string
|
||||||
|
|
||||||
|
func (v V) GreaterThanOrEqualTo(other V) bool {
|
||||||
|
return !v.LessThan(other)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v V) LessThan(other V) bool {
|
||||||
|
leftParts, leftCount := split(v)
|
||||||
|
rightParts, rightCount := split(other)
|
||||||
|
|
||||||
|
var length int
|
||||||
|
if leftCount < rightCount {
|
||||||
|
length = rightCount
|
||||||
|
} else {
|
||||||
|
length = leftCount
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
var left, right string
|
||||||
|
|
||||||
|
if i < leftCount {
|
||||||
|
left = leftParts[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if i < rightCount {
|
||||||
|
right = rightParts[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
if left == right {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
leftInt := parseInt(left)
|
||||||
|
rightInt := parseInt(right)
|
||||||
|
|
||||||
|
isNumericalComparison := leftInt != nil && rightInt != nil
|
||||||
|
|
||||||
|
if isNumericalComparison {
|
||||||
|
return *leftInt < *rightInt
|
||||||
|
}
|
||||||
|
|
||||||
|
return left < right
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func split(v V) ([]string, int) {
|
||||||
|
var chunks []string
|
||||||
|
|
||||||
|
for _, part := range strings.Split(string(v), ".") {
|
||||||
|
chunks = append(chunks, splitNumericalChunks(part)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chunks, len(chunks)
|
||||||
|
}
|
||||||
|
|
||||||
|
var numericalOrAlphaRE = regexp.MustCompile(`(\d+|\D+)`)
|
||||||
|
|
||||||
|
func splitNumericalChunks(s string) []string {
|
||||||
|
return numericalOrAlphaRE.FindAllString(s, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseInt(s string) *int64 {
|
||||||
|
if n, err := strconv.ParseInt(s, 10, 64); err == nil {
|
||||||
|
return &n
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
58
plugin/checker/internal/version/version_test.go
Обычный файл
58
plugin/checker/internal/version/version_test.go
Обычный файл
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package version
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestVersionComparison(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
a, b V
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
a: V("1.2"),
|
||||||
|
b: V("1.10"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: V("1.2.1"),
|
||||||
|
b: V("1.2.3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: V("1.2"),
|
||||||
|
b: V("1.2.3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: V("1.2.1"),
|
||||||
|
b: V("1.2.3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: V("1.1"),
|
||||||
|
b: V("1.2.3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: V("1.2.3"),
|
||||||
|
b: V("1.3"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
a: V("1.2.1-rc2"),
|
||||||
|
b: V("1.2.1-rc10"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(fmt.Sprintf("%+v", tc), func(t *testing.T) {
|
||||||
|
assert.True(t, tc.a.LessThan(tc.b))
|
||||||
|
assert.False(t, tc.b.LessThan(tc.a))
|
||||||
|
|
||||||
|
assert.True(t, tc.b.GreaterThanOrEqualTo(tc.a))
|
||||||
|
assert.False(t, tc.a.GreaterThanOrEqualTo(tc.b))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.True(t, V("1.2").GreaterThanOrEqualTo("1.2"))
|
||||||
|
}
|
||||||
@@ -4,123 +4,69 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"sort"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go/ast"
|
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const pluginPackagePath = "github.com/mattermost/mattermost-server/plugin"
|
const pluginPackagePath = "github.com/mattermost/mattermost-server/plugin"
|
||||||
|
|
||||||
|
type result struct {
|
||||||
|
Warnings []string
|
||||||
|
Errors []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type checkFn func(pkgPath string) (result, error)
|
||||||
|
|
||||||
|
var checks = []checkFn{
|
||||||
|
checkAPIVersionComments,
|
||||||
|
checkHelpersVersionComments,
|
||||||
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := runCheck(pluginPackagePath); err != nil {
|
var res result
|
||||||
|
for _, check := range checks {
|
||||||
|
res = runCheck(res, check)
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgs []string
|
||||||
|
msgs = append(msgs, res.Errors...)
|
||||||
|
msgs = append(msgs, res.Warnings...)
|
||||||
|
sort.Strings(msgs)
|
||||||
|
|
||||||
|
if len(msgs) > 0 {
|
||||||
fmt.Fprintln(os.Stderr, "#", pluginPackagePath)
|
fmt.Fprintln(os.Stderr, "#", pluginPackagePath)
|
||||||
fmt.Fprintln(os.Stderr, err)
|
fmt.Fprintln(os.Stderr, strings.Join(msgs, "\n"))
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(res.Errors) > 0 {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runCheck(pkgPath string) error {
|
func runCheck(prev result, fn checkFn) result {
|
||||||
pkg, err := getPackage(pkgPath)
|
res, err := fn(pluginPackagePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
prev.Errors = append(prev.Errors, err.Error())
|
||||||
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
apiInterface := findAPIInterface(pkg.Syntax)
|
if len(res.Warnings) > 0 {
|
||||||
if apiInterface == nil {
|
prev.Warnings = append(prev.Warnings, mapWarnings(res.Warnings)...)
|
||||||
return errors.Errorf("could not find API interface in package %s", pkgPath)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
invalidMethods := findInvalidMethods(apiInterface.Methods.List)
|
if len(res.Errors) > 0 {
|
||||||
if len(invalidMethods) > 0 {
|
prev.Errors = append(prev.Errors, res.Errors...)
|
||||||
return errors.New(renderErrorMessage(pkg, invalidMethods))
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
|
return prev
|
||||||
}
|
}
|
||||||
|
|
||||||
func getPackage(pkgPath string) (*packages.Package, error) {
|
func mapWarnings(ss []string) []string {
|
||||||
cfg := &packages.Config{
|
var out []string
|
||||||
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax,
|
for _, s := range ss {
|
||||||
|
out = append(out, "[warn] "+s)
|
||||||
}
|
}
|
||||||
pkgs, err := packages.Load(cfg, pkgPath)
|
return out
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(pkgs) == 0 {
|
|
||||||
return nil, errors.Errorf("could not find package %s", pkgPath)
|
|
||||||
}
|
|
||||||
return pkgs[0], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findAPIInterface(files []*ast.File) *ast.InterfaceType {
|
|
||||||
for _, f := range files {
|
|
||||||
var iface *ast.InterfaceType
|
|
||||||
|
|
||||||
ast.Inspect(f, func(n ast.Node) bool {
|
|
||||||
if t, ok := n.(*ast.TypeSpec); ok {
|
|
||||||
if i, ok := t.Type.(*ast.InterfaceType); ok && t.Name.Name == "API" {
|
|
||||||
iface = i
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
|
|
||||||
if iface != nil {
|
|
||||||
return iface
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func findInvalidMethods(methods []*ast.Field) []*ast.Field {
|
|
||||||
var invalid []*ast.Field
|
|
||||||
for _, m := range methods {
|
|
||||||
if !hasValidMinimumVersionComment(m.Doc.Text()) {
|
|
||||||
invalid = append(invalid, m)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return invalid
|
|
||||||
}
|
|
||||||
|
|
||||||
var versionRequirementRE = regexp.MustCompile(`^Minimum server version: \d+\.\d+(\.\d+)?$`)
|
|
||||||
|
|
||||||
func hasValidMinimumVersionComment(s string) bool {
|
|
||||||
lines := strings.Split(strings.TrimSpace(s), "\n")
|
|
||||||
if len(lines) > 0 {
|
|
||||||
lastLine := lines[len(lines)-1]
|
|
||||||
return versionRequirementRE.MatchString(lastLine)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func renderErrorMessage(pkg *packages.Package, methods []*ast.Field) string {
|
|
||||||
cwd, _ := os.Getwd()
|
|
||||||
out := &bytes.Buffer{}
|
|
||||||
|
|
||||||
for _, m := range methods {
|
|
||||||
pos := pkg.Fset.Position(m.Pos())
|
|
||||||
filename, err := filepath.Rel(cwd, pos.Filename)
|
|
||||||
if err != nil {
|
|
||||||
// If deriving a relative path fails for some reason,
|
|
||||||
// we prefer to still print the absolute path to the file.
|
|
||||||
filename = pos.Filename
|
|
||||||
}
|
|
||||||
fmt.Fprintf(out,
|
|
||||||
"%s:%d:%d: missing a minimum server version comment\n",
|
|
||||||
filename,
|
|
||||||
pos.Line,
|
|
||||||
pos.Column,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return out.String()
|
|
||||||
}
|
}
|
||||||
|
|||||||
29
plugin/checker/render.go
Обычный файл
29
plugin/checker/render.go
Обычный файл
@@ -0,0 +1,29 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"go/token"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
func renderWithFilePosition(fset *token.FileSet, pos token.Pos, msg string) string {
|
||||||
|
var cwd string
|
||||||
|
if d, err := os.Getwd(); err == nil {
|
||||||
|
cwd = d
|
||||||
|
}
|
||||||
|
|
||||||
|
fpos := fset.Position(pos)
|
||||||
|
|
||||||
|
filename, err := filepath.Rel(cwd, fpos.Filename)
|
||||||
|
if err != nil {
|
||||||
|
// If deriving a relative path fails for some reason,
|
||||||
|
// we prefer to still print the absolute path to the file.
|
||||||
|
filename = fpos.Filename
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("%s:%d:%d: %s", filename, fpos.Line, fpos.Column, msg)
|
||||||
|
}
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package valid
|
|
||||||
|
|
||||||
type API interface {
|
|
||||||
// ValidMethod is a fake method for testing the
|
|
||||||
// plugin comment checker with a valid comment.
|
|
||||||
//
|
|
||||||
// Minimum server version: 1.2.3
|
|
||||||
ValidMethod()
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/dyatlov/go-opengraph/opengraph"
|
"github.com/dyatlov/go-opengraph/opengraph"
|
||||||
plugin "github.com/hashicorp/go-plugin"
|
"github.com/hashicorp/go-plugin"
|
||||||
"github.com/mattermost/mattermost-server/mlog"
|
"github.com/mattermost/mattermost-server/mlog"
|
||||||
"github.com/mattermost/mattermost-server/model"
|
"github.com/mattermost/mattermost-server/model"
|
||||||
)
|
)
|
||||||
@@ -531,3 +531,118 @@ func (s *hooksRPCServer) MessageWillBeUpdated(args *Z_MessageWillBeUpdatedArgs,
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Z_LogDebugArgs struct {
|
||||||
|
A string
|
||||||
|
B []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogDebugReturns struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *apiRPCClient) LogDebug(msg string, keyValuePairs ...interface{}) {
|
||||||
|
stringifiedPairs := stringifyToObjects(keyValuePairs)
|
||||||
|
_args := &Z_LogDebugArgs{msg, stringifiedPairs}
|
||||||
|
_returns := &Z_LogDebugReturns{}
|
||||||
|
if err := g.client.Call("Plugin.LogDebug", _args, _returns); err != nil {
|
||||||
|
log.Printf("RPC call to LogDebug API failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiRPCServer) LogDebug(args *Z_LogDebugArgs, returns *Z_LogDebugReturns) error {
|
||||||
|
if hook, ok := s.impl.(interface {
|
||||||
|
LogDebug(msg string, keyValuePairs ...interface{})
|
||||||
|
}); ok {
|
||||||
|
hook.LogDebug(args.A, args.B...)
|
||||||
|
} else {
|
||||||
|
return encodableError(fmt.Errorf("API LogDebug called but not implemented."))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogInfoArgs struct {
|
||||||
|
A string
|
||||||
|
B []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogInfoReturns struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *apiRPCClient) LogInfo(msg string, keyValuePairs ...interface{}) {
|
||||||
|
stringifiedPairs := stringifyToObjects(keyValuePairs)
|
||||||
|
_args := &Z_LogInfoArgs{msg, stringifiedPairs}
|
||||||
|
_returns := &Z_LogInfoReturns{}
|
||||||
|
if err := g.client.Call("Plugin.LogInfo", _args, _returns); err != nil {
|
||||||
|
log.Printf("RPC call to LogInfo API failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiRPCServer) LogInfo(args *Z_LogInfoArgs, returns *Z_LogInfoReturns) error {
|
||||||
|
if hook, ok := s.impl.(interface {
|
||||||
|
LogInfo(msg string, keyValuePairs ...interface{})
|
||||||
|
}); ok {
|
||||||
|
hook.LogInfo(args.A, args.B...)
|
||||||
|
} else {
|
||||||
|
return encodableError(fmt.Errorf("API LogInfo called but not implemented."))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogWarnArgs struct {
|
||||||
|
A string
|
||||||
|
B []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogWarnReturns struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *apiRPCClient) LogWarn(msg string, keyValuePairs ...interface{}) {
|
||||||
|
stringifiedPairs := stringifyToObjects(keyValuePairs)
|
||||||
|
_args := &Z_LogWarnArgs{msg, stringifiedPairs}
|
||||||
|
_returns := &Z_LogWarnReturns{}
|
||||||
|
if err := g.client.Call("Plugin.LogWarn", _args, _returns); err != nil {
|
||||||
|
log.Printf("RPC call to LogWarn API failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiRPCServer) LogWarn(args *Z_LogWarnArgs, returns *Z_LogWarnReturns) error {
|
||||||
|
if hook, ok := s.impl.(interface {
|
||||||
|
LogWarn(msg string, keyValuePairs ...interface{})
|
||||||
|
}); ok {
|
||||||
|
hook.LogWarn(args.A, args.B...)
|
||||||
|
} else {
|
||||||
|
return encodableError(fmt.Errorf("API LogWarn called but not implemented."))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogErrorArgs struct {
|
||||||
|
A string
|
||||||
|
B []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Z_LogErrorReturns struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *apiRPCClient) LogError(msg string, keyValuePairs ...interface{}) {
|
||||||
|
stringifiedPairs := stringifyToObjects(keyValuePairs)
|
||||||
|
_args := &Z_LogErrorArgs{msg, stringifiedPairs}
|
||||||
|
_returns := &Z_LogErrorReturns{}
|
||||||
|
if err := g.client.Call("Plugin.LogError", _args, _returns); err != nil {
|
||||||
|
log.Printf("RPC call to LogError API failed: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *apiRPCServer) LogError(args *Z_LogErrorArgs, returns *Z_LogErrorReturns) error {
|
||||||
|
if hook, ok := s.impl.(interface {
|
||||||
|
LogError(msg string, keyValuePairs ...interface{})
|
||||||
|
}); ok {
|
||||||
|
hook.LogError(args.A, args.B...)
|
||||||
|
} else {
|
||||||
|
return encodableError(fmt.Errorf("API LogError called but not implemented."))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -3989,118 +3989,6 @@ func (s *apiRPCServer) HasPermissionToChannel(args *Z_HasPermissionToChannelArgs
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Z_LogDebugArgs struct {
|
|
||||||
A string
|
|
||||||
B []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogDebugReturns struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *apiRPCClient) LogDebug(msg string, keyValuePairs ...interface{}) {
|
|
||||||
_args := &Z_LogDebugArgs{msg, keyValuePairs}
|
|
||||||
_returns := &Z_LogDebugReturns{}
|
|
||||||
if err := g.client.Call("Plugin.LogDebug", _args, _returns); err != nil {
|
|
||||||
log.Printf("RPC call to LogDebug API failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *apiRPCServer) LogDebug(args *Z_LogDebugArgs, returns *Z_LogDebugReturns) error {
|
|
||||||
if hook, ok := s.impl.(interface {
|
|
||||||
LogDebug(msg string, keyValuePairs ...interface{})
|
|
||||||
}); ok {
|
|
||||||
hook.LogDebug(args.A, args.B...)
|
|
||||||
} else {
|
|
||||||
return encodableError(fmt.Errorf("API LogDebug called but not implemented."))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogInfoArgs struct {
|
|
||||||
A string
|
|
||||||
B []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogInfoReturns struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *apiRPCClient) LogInfo(msg string, keyValuePairs ...interface{}) {
|
|
||||||
_args := &Z_LogInfoArgs{msg, keyValuePairs}
|
|
||||||
_returns := &Z_LogInfoReturns{}
|
|
||||||
if err := g.client.Call("Plugin.LogInfo", _args, _returns); err != nil {
|
|
||||||
log.Printf("RPC call to LogInfo API failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *apiRPCServer) LogInfo(args *Z_LogInfoArgs, returns *Z_LogInfoReturns) error {
|
|
||||||
if hook, ok := s.impl.(interface {
|
|
||||||
LogInfo(msg string, keyValuePairs ...interface{})
|
|
||||||
}); ok {
|
|
||||||
hook.LogInfo(args.A, args.B...)
|
|
||||||
} else {
|
|
||||||
return encodableError(fmt.Errorf("API LogInfo called but not implemented."))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogErrorArgs struct {
|
|
||||||
A string
|
|
||||||
B []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogErrorReturns struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *apiRPCClient) LogError(msg string, keyValuePairs ...interface{}) {
|
|
||||||
_args := &Z_LogErrorArgs{msg, keyValuePairs}
|
|
||||||
_returns := &Z_LogErrorReturns{}
|
|
||||||
if err := g.client.Call("Plugin.LogError", _args, _returns); err != nil {
|
|
||||||
log.Printf("RPC call to LogError API failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *apiRPCServer) LogError(args *Z_LogErrorArgs, returns *Z_LogErrorReturns) error {
|
|
||||||
if hook, ok := s.impl.(interface {
|
|
||||||
LogError(msg string, keyValuePairs ...interface{})
|
|
||||||
}); ok {
|
|
||||||
hook.LogError(args.A, args.B...)
|
|
||||||
} else {
|
|
||||||
return encodableError(fmt.Errorf("API LogError called but not implemented."))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogWarnArgs struct {
|
|
||||||
A string
|
|
||||||
B []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_LogWarnReturns struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *apiRPCClient) LogWarn(msg string, keyValuePairs ...interface{}) {
|
|
||||||
_args := &Z_LogWarnArgs{msg, keyValuePairs}
|
|
||||||
_returns := &Z_LogWarnReturns{}
|
|
||||||
if err := g.client.Call("Plugin.LogWarn", _args, _returns); err != nil {
|
|
||||||
log.Printf("RPC call to LogWarn API failed: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *apiRPCServer) LogWarn(args *Z_LogWarnArgs, returns *Z_LogWarnReturns) error {
|
|
||||||
if hook, ok := s.impl.(interface {
|
|
||||||
LogWarn(msg string, keyValuePairs ...interface{})
|
|
||||||
}); ok {
|
|
||||||
hook.LogWarn(args.A, args.B...)
|
|
||||||
} else {
|
|
||||||
return encodableError(fmt.Errorf("API LogWarn called but not implemented."))
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type Z_SendMailArgs struct {
|
type Z_SendMailArgs struct {
|
||||||
A string
|
A string
|
||||||
B string
|
B string
|
||||||
|
|||||||
@@ -8,9 +8,13 @@ import "github.com/mattermost/mattermost-server/model"
|
|||||||
type Helpers interface {
|
type Helpers interface {
|
||||||
// EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot.
|
// EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot.
|
||||||
// Returns the id of the resulting bot.
|
// Returns the id of the resulting bot.
|
||||||
|
//
|
||||||
|
// Minimum server version: 5.10
|
||||||
EnsureBot(bot *model.Bot) (string, error)
|
EnsureBot(bot *model.Bot) (string, error)
|
||||||
|
|
||||||
// KVSetJSON stores a key-value pair, unique per plugin, marshalling the given value as a JSON string.
|
// KVSetJSON stores a key-value pair, unique per plugin, marshalling the given value as a JSON string.
|
||||||
|
//
|
||||||
|
// Minimum server version: 5.2
|
||||||
KVSetJSON(key string, value interface{}) error
|
KVSetJSON(key string, value interface{}) error
|
||||||
|
|
||||||
// KVCompareAndSetJSON updates a key-value pair, unique per plugin, but only if the current value matches the given oldValue after marshalling as a JSON string.
|
// KVCompareAndSetJSON updates a key-value pair, unique per plugin, but only if the current value matches the given oldValue after marshalling as a JSON string.
|
||||||
@@ -31,6 +35,8 @@ type Helpers interface {
|
|||||||
KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error)
|
KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error)
|
||||||
|
|
||||||
// KVGetJSON retrieves a value based on the key, unique per plugin, unmarshalling the previously set JSON string into the given value. Returns true if the key exists.
|
// KVGetJSON retrieves a value based on the key, unique per plugin, unmarshalling the previously set JSON string into the given value. Returns true if the key exists.
|
||||||
|
//
|
||||||
|
// Minimum server version: 5.2
|
||||||
KVGetJSON(key string, value interface{}) (bool, error)
|
KVGetJSON(key string, value interface{}) (bool, error)
|
||||||
|
|
||||||
// KVSetWithExpiryJSON stores a key-value pair with an expiry time, unique per plugin, marshalling the given value as a JSON string.
|
// KVSetWithExpiryJSON stores a key-value pair with an expiry time, unique per plugin, marshalling the given value as a JSON string.
|
||||||
|
|||||||
@@ -399,6 +399,10 @@ func removeExcluded(info *PluginInterfaceInfo) *PluginInterfaceInfo {
|
|||||||
"FileWillBeUploaded",
|
"FileWillBeUploaded",
|
||||||
"MessageWillBePosted",
|
"MessageWillBePosted",
|
||||||
"MessageWillBeUpdated",
|
"MessageWillBeUpdated",
|
||||||
|
"LogDebug",
|
||||||
|
"LogInfo",
|
||||||
|
"LogWarn",
|
||||||
|
"LogError",
|
||||||
}
|
}
|
||||||
for _, exclusion := range excluded {
|
for _, exclusion := range excluded {
|
||||||
if exclusion == item {
|
if exclusion == item {
|
||||||
|
|||||||
31
plugin/stringifier.go
Обычный файл
31
plugin/stringifier.go
Обычный файл
@@ -0,0 +1,31 @@
|
|||||||
|
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func stringify(objects []interface{}) []string {
|
||||||
|
stringified := make([]string, len(objects), len(objects))
|
||||||
|
for i, object := range objects {
|
||||||
|
stringified[i] = fmt.Sprintf("%+v", object)
|
||||||
|
}
|
||||||
|
return stringified
|
||||||
|
}
|
||||||
|
|
||||||
|
func toObjects(strings []string) []interface{} {
|
||||||
|
if strings == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
objects := make([]interface{}, len(strings))
|
||||||
|
for i, string := range strings {
|
||||||
|
objects[i] = string
|
||||||
|
}
|
||||||
|
return objects
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringifyToObjects(objects []interface{}) []interface{} {
|
||||||
|
return toObjects(stringify(objects))
|
||||||
|
}
|
||||||
93
plugin/stringifier_test.go
Обычный файл
93
plugin/stringifier_test.go
Обычный файл
@@ -0,0 +1,93 @@
|
|||||||
|
// Copyright (c) 2019-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See License.txt for license information.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringify(t *testing.T) {
|
||||||
|
t.Run("NilShouldReturnEmpty", func(t *testing.T) {
|
||||||
|
strings := stringify(nil)
|
||||||
|
assert.Empty(t, strings)
|
||||||
|
})
|
||||||
|
t.Run("EmptyShouldReturnEmpty", func(t *testing.T) {
|
||||||
|
strings := stringify(make([]interface{}, 0, 0))
|
||||||
|
assert.Empty(t, strings)
|
||||||
|
})
|
||||||
|
t.Run("PrimitivesAndCompositesShouldReturnCorrectValues", func(t *testing.T) {
|
||||||
|
strings := stringify([]interface{}{
|
||||||
|
1234,
|
||||||
|
3.14159265358979323846264338327950288419716939937510,
|
||||||
|
true,
|
||||||
|
"foo",
|
||||||
|
nil,
|
||||||
|
[]string{"foo", "bar"},
|
||||||
|
map[string]int{"one": 1, "two": 2},
|
||||||
|
&WithString{},
|
||||||
|
&WithoutString{},
|
||||||
|
&WithStringAndError{},
|
||||||
|
})
|
||||||
|
assert.Equal(t, []string{
|
||||||
|
"1234",
|
||||||
|
"3.141592653589793",
|
||||||
|
"true",
|
||||||
|
"foo",
|
||||||
|
"<nil>",
|
||||||
|
"[foo bar]",
|
||||||
|
"map[one:1 two:2]",
|
||||||
|
"string",
|
||||||
|
"&{}",
|
||||||
|
"error",
|
||||||
|
}, strings)
|
||||||
|
})
|
||||||
|
t.Run("ErrorShouldReturnFormattedStack", func(t *testing.T) {
|
||||||
|
strings := stringify([]interface{}{
|
||||||
|
errors.New("error"),
|
||||||
|
errors.WithStack(errors.New("error")),
|
||||||
|
})
|
||||||
|
stackRegexp := "error\n.*plugin.TestStringify.func\\d+\n\t.*plugin/stringifier_test.go:\\d+\ntesting.tRunner\n\t.*testing.go:\\d+.*"
|
||||||
|
assert.Len(t, strings, 2)
|
||||||
|
assert.Regexp(t, stackRegexp, strings[0])
|
||||||
|
assert.Regexp(t, stackRegexp, strings[1])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithString struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*WithString) String() string {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithoutString struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
type WithStringAndError struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*WithStringAndError) String() string {
|
||||||
|
return "string"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*WithStringAndError) Error() string {
|
||||||
|
return "error"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToObjects(t *testing.T) {
|
||||||
|
t.Run("NilShouldReturnNil", func(t *testing.T) {
|
||||||
|
objects := toObjects(nil)
|
||||||
|
assert.Nil(t, objects)
|
||||||
|
})
|
||||||
|
t.Run("EmptyShouldReturnEmpty", func(t *testing.T) {
|
||||||
|
objects := toObjects(make([]string, 0, 0))
|
||||||
|
assert.Empty(t, objects)
|
||||||
|
})
|
||||||
|
t.Run("ShouldReturnSliceOfObjects", func(t *testing.T) {
|
||||||
|
objects := toObjects([]string{"foo", "bar"})
|
||||||
|
assert.Equal(t, []interface{}{"foo", "bar"}, objects)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -7,11 +7,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/einterfaces"
|
"github.com/mattermost/mattermost-server/einterfaces"
|
||||||
"github.com/mattermost/mattermost-server/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ENABLE_EXPERIMENTAL_REDIS = false
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type LayeredStoreDatabaseLayer interface {
|
type LayeredStoreDatabaseLayer interface {
|
||||||
@@ -23,7 +18,6 @@ type LayeredStore struct {
|
|||||||
TmpContext context.Context
|
TmpContext context.Context
|
||||||
DatabaseLayer LayeredStoreDatabaseLayer
|
DatabaseLayer LayeredStoreDatabaseLayer
|
||||||
LocalCacheLayer *LocalCacheSupplier
|
LocalCacheLayer *LocalCacheSupplier
|
||||||
RedisLayer *RedisSupplier
|
|
||||||
LayerChainHead LayeredStoreSupplier
|
LayerChainHead LayeredStoreSupplier
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,15 +29,8 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Setup the chain
|
// Setup the chain
|
||||||
if ENABLE_EXPERIMENTAL_REDIS {
|
store.LocalCacheLayer.SetChainNext(store.DatabaseLayer)
|
||||||
mlog.Debug("Experimental redis enabled.")
|
store.LayerChainHead = store.LocalCacheLayer
|
||||||
store.RedisLayer = NewRedisSupplier()
|
|
||||||
store.RedisLayer.SetChainNext(store.DatabaseLayer)
|
|
||||||
store.LayerChainHead = store.RedisLayer
|
|
||||||
} else {
|
|
||||||
store.LocalCacheLayer.SetChainNext(store.DatabaseLayer)
|
|
||||||
store.LayerChainHead = store.LocalCacheLayer
|
|
||||||
}
|
|
||||||
|
|
||||||
return store
|
return store
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,87 +0,0 @@
|
|||||||
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
|
|
||||||
// See License.txt for license information.
|
|
||||||
|
|
||||||
package store
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis"
|
|
||||||
"github.com/mattermost/mattermost-server/mlog"
|
|
||||||
)
|
|
||||||
|
|
||||||
const REDIS_EXPIRY_TIME = 30 * time.Minute
|
|
||||||
|
|
||||||
type RedisSupplier struct {
|
|
||||||
next LayeredStoreSupplier
|
|
||||||
client *redis.Client
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetBytes(key interface{}) ([]byte, error) {
|
|
||||||
var buf bytes.Buffer
|
|
||||||
enc := gob.NewEncoder(&buf)
|
|
||||||
err := enc.Encode(key)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return buf.Bytes(), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func DecodeBytes(input []byte, thing interface{}) error {
|
|
||||||
dec := gob.NewDecoder(bytes.NewReader(input))
|
|
||||||
return dec.Decode(thing)
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRedisSupplier() *RedisSupplier {
|
|
||||||
supplier := &RedisSupplier{}
|
|
||||||
|
|
||||||
supplier.client = redis.NewClient(&redis.Options{
|
|
||||||
Addr: "localhost:6379",
|
|
||||||
Password: "",
|
|
||||||
DB: 0,
|
|
||||||
})
|
|
||||||
|
|
||||||
if _, err := supplier.client.Ping().Result(); err != nil {
|
|
||||||
mlog.Error("Unable to ping redis server: " + err.Error())
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return supplier
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RedisSupplier) save(key string, value interface{}, expiry time.Duration) error {
|
|
||||||
if bytes, err := GetBytes(value); err != nil {
|
|
||||||
return err
|
|
||||||
} else {
|
|
||||||
if err := s.client.Set(key, bytes, expiry).Err(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RedisSupplier) load(key string, writeTo interface{}) (bool, error) {
|
|
||||||
if data, err := s.client.Get(key).Bytes(); err != nil {
|
|
||||||
if err == redis.Nil {
|
|
||||||
return false, nil
|
|
||||||
} else {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := DecodeBytes(data, writeTo); err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RedisSupplier) SetChainNext(next LayeredStoreSupplier) {
|
|
||||||
s.next = next
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *RedisSupplier) Next() LayeredStoreSupplier {
|
|
||||||
return s.next
|
|
||||||
}
|
|
||||||
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
@@ -382,9 +382,8 @@ func testOAuthStoreDeleteApp(t *testing.T, ss store.Store) {
|
|||||||
err = ss.OAuth().DeleteApp(a1.Id)
|
err = ss.OAuth().DeleteApp(a1.Id)
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
|
|
||||||
if _, err = ss.Session().Get(s1.Token); err == nil {
|
_, err = ss.Session().Get(s1.Token)
|
||||||
t.Fatal("should error - session should be deleted")
|
require.NotNil(t, err, "should error - session should be deleted")
|
||||||
}
|
|
||||||
|
|
||||||
_, err = ss.OAuth().GetAccessData(s1.Token)
|
_, err = ss.OAuth().GetAccessData(s1.Token)
|
||||||
require.NotNil(t, err, "should error - access data should be deleted")
|
require.NotNil(t, err, "should error - access data should be deleted")
|
||||||
|
|||||||
2
vendor/github.com/go-redis/redis/.gitignore
сгенерированный
поставляемый
2
vendor/github.com/go-redis/redis/.gitignore
сгенерированный
поставляемый
@@ -1,2 +0,0 @@
|
|||||||
*.rdb
|
|
||||||
testdata/*/
|
|
||||||
20
vendor/github.com/go-redis/redis/.travis.yml
сгенерированный
поставляемый
20
vendor/github.com/go-redis/redis/.travis.yml
сгенерированный
поставляемый
@@ -1,20 +0,0 @@
|
|||||||
sudo: false
|
|
||||||
language: go
|
|
||||||
|
|
||||||
services:
|
|
||||||
- redis-server
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.9.x
|
|
||||||
- 1.10.x
|
|
||||||
- 1.11.x
|
|
||||||
- 1.12.x
|
|
||||||
- tip
|
|
||||||
|
|
||||||
matrix:
|
|
||||||
allow_failures:
|
|
||||||
- go: tip
|
|
||||||
|
|
||||||
install:
|
|
||||||
- go get github.com/onsi/ginkgo
|
|
||||||
- go get github.com/onsi/gomega
|
|
||||||
25
vendor/github.com/go-redis/redis/CHANGELOG.md
сгенерированный
поставляемый
25
vendor/github.com/go-redis/redis/CHANGELOG.md
сгенерированный
поставляемый
@@ -1,25 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
## Unreleased
|
|
||||||
|
|
||||||
- Cluster and Ring pipelines process commands for each node in its own goroutine.
|
|
||||||
|
|
||||||
## 6.14
|
|
||||||
|
|
||||||
- Added Options.MinIdleConns.
|
|
||||||
- Added Options.MaxConnAge.
|
|
||||||
- PoolStats.FreeConns is renamed to PoolStats.IdleConns.
|
|
||||||
- Add Client.Do to simplify creating custom commands.
|
|
||||||
- Add Cmd.String, Cmd.Int, Cmd.Int64, Cmd.Uint64, Cmd.Float64, and Cmd.Bool helpers.
|
|
||||||
- Lower memory usage.
|
|
||||||
|
|
||||||
## v6.13
|
|
||||||
|
|
||||||
- Ring got new options called `HashReplicas` and `Hash`. It is recommended to set `HashReplicas = 1000` for better keys distribution between shards.
|
|
||||||
- Cluster client was optimized to use much less memory when reloading cluster state.
|
|
||||||
- PubSub.ReceiveMessage is re-worked to not use ReceiveTimeout so it does not lose data when timeout occurres. In most cases it is recommended to use PubSub.Channel instead.
|
|
||||||
- Dialer.KeepAlive is set to 5 minutes by default.
|
|
||||||
|
|
||||||
## v6.12
|
|
||||||
|
|
||||||
- ClusterClient got new option called `ClusterSlots` which allows to build cluster of normal Redis Servers that don't have cluster mode enabled. See https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup
|
|
||||||
25
vendor/github.com/go-redis/redis/LICENSE
сгенерированный
поставляемый
25
vendor/github.com/go-redis/redis/LICENSE
сгенерированный
поставляемый
@@ -1,25 +0,0 @@
|
|||||||
Copyright (c) 2013 The github.com/go-redis/redis Authors.
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are
|
|
||||||
met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright
|
|
||||||
notice, this list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above
|
|
||||||
copyright notice, this list of conditions and the following disclaimer
|
|
||||||
in the documentation and/or other materials provided with the
|
|
||||||
distribution.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
22
vendor/github.com/go-redis/redis/Makefile
сгенерированный
поставляемый
22
vendor/github.com/go-redis/redis/Makefile
сгенерированный
поставляемый
@@ -1,22 +0,0 @@
|
|||||||
all: testdeps
|
|
||||||
go test ./...
|
|
||||||
go test ./... -short -race
|
|
||||||
env GOOS=linux GOARCH=386 go test ./...
|
|
||||||
go vet
|
|
||||||
go get github.com/gordonklaus/ineffassign
|
|
||||||
ineffassign .
|
|
||||||
|
|
||||||
testdeps: testdata/redis/src/redis-server
|
|
||||||
|
|
||||||
bench: testdeps
|
|
||||||
go test ./... -test.run=NONE -test.bench=. -test.benchmem
|
|
||||||
|
|
||||||
.PHONY: all test testdeps bench
|
|
||||||
|
|
||||||
testdata/redis:
|
|
||||||
mkdir -p $@
|
|
||||||
wget -qO- https://github.com/antirez/redis/archive/5.0.tar.gz | tar xvz --strip-components=1 -C $@
|
|
||||||
|
|
||||||
testdata/redis/src/redis-server: testdata/redis
|
|
||||||
sed -i.bak 's/libjemalloc.a/libjemalloc.a -lrt/g' $</src/Makefile
|
|
||||||
cd $< && make all
|
|
||||||
146
vendor/github.com/go-redis/redis/README.md
сгенерированный
поставляемый
146
vendor/github.com/go-redis/redis/README.md
сгенерированный
поставляемый
@@ -1,146 +0,0 @@
|
|||||||
# Redis client for Golang
|
|
||||||
|
|
||||||
[](https://travis-ci.org/go-redis/redis)
|
|
||||||
[](https://godoc.org/github.com/go-redis/redis)
|
|
||||||
[](https://airbrake.io)
|
|
||||||
|
|
||||||
Supports:
|
|
||||||
|
|
||||||
- Redis 3 commands except QUIT, MONITOR, SLOWLOG and SYNC.
|
|
||||||
- Automatic connection pooling with [circuit breaker](https://en.wikipedia.org/wiki/Circuit_breaker_design_pattern) support.
|
|
||||||
- [Pub/Sub](https://godoc.org/github.com/go-redis/redis#PubSub).
|
|
||||||
- [Transactions](https://godoc.org/github.com/go-redis/redis#example-Client-TxPipeline).
|
|
||||||
- [Pipeline](https://godoc.org/github.com/go-redis/redis#example-Client-Pipeline) and [TxPipeline](https://godoc.org/github.com/go-redis/redis#example-Client-TxPipeline).
|
|
||||||
- [Scripting](https://godoc.org/github.com/go-redis/redis#Script).
|
|
||||||
- [Timeouts](https://godoc.org/github.com/go-redis/redis#Options).
|
|
||||||
- [Redis Sentinel](https://godoc.org/github.com/go-redis/redis#NewFailoverClient).
|
|
||||||
- [Redis Cluster](https://godoc.org/github.com/go-redis/redis#NewClusterClient).
|
|
||||||
- [Cluster of Redis Servers](https://godoc.org/github.com/go-redis/redis#example-NewClusterClient--ManualSetup) without using cluster mode and Redis Sentinel.
|
|
||||||
- [Ring](https://godoc.org/github.com/go-redis/redis#NewRing).
|
|
||||||
- [Instrumentation](https://godoc.org/github.com/go-redis/redis#ex-package--Instrumentation).
|
|
||||||
- [Cache friendly](https://github.com/go-redis/cache).
|
|
||||||
- [Rate limiting](https://github.com/go-redis/redis_rate).
|
|
||||||
- [Distributed Locks](https://github.com/bsm/redis-lock).
|
|
||||||
|
|
||||||
API docs: https://godoc.org/github.com/go-redis/redis.
|
|
||||||
Examples: https://godoc.org/github.com/go-redis/redis#pkg-examples.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Install:
|
|
||||||
|
|
||||||
```shell
|
|
||||||
go get -u github.com/go-redis/redis
|
|
||||||
```
|
|
||||||
|
|
||||||
Import:
|
|
||||||
|
|
||||||
```go
|
|
||||||
import "github.com/go-redis/redis"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Quickstart
|
|
||||||
|
|
||||||
```go
|
|
||||||
func ExampleNewClient() {
|
|
||||||
client := redis.NewClient(&redis.Options{
|
|
||||||
Addr: "localhost:6379",
|
|
||||||
Password: "", // no password set
|
|
||||||
DB: 0, // use default DB
|
|
||||||
})
|
|
||||||
|
|
||||||
pong, err := client.Ping().Result()
|
|
||||||
fmt.Println(pong, err)
|
|
||||||
// Output: PONG <nil>
|
|
||||||
}
|
|
||||||
|
|
||||||
func ExampleClient() {
|
|
||||||
err := client.Set("key", "value", 0).Err()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
val, err := client.Get("key").Result()
|
|
||||||
if err != nil {
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
fmt.Println("key", val)
|
|
||||||
|
|
||||||
val2, err := client.Get("key2").Result()
|
|
||||||
if err == redis.Nil {
|
|
||||||
fmt.Println("key2 does not exist")
|
|
||||||
} else if err != nil {
|
|
||||||
panic(err)
|
|
||||||
} else {
|
|
||||||
fmt.Println("key2", val2)
|
|
||||||
}
|
|
||||||
// Output: key value
|
|
||||||
// key2 does not exist
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Howto
|
|
||||||
|
|
||||||
Please go through [examples](https://godoc.org/github.com/go-redis/redis#pkg-examples) to get an idea how to use this package.
|
|
||||||
|
|
||||||
## Look and feel
|
|
||||||
|
|
||||||
Some corner cases:
|
|
||||||
|
|
||||||
```go
|
|
||||||
// SET key value EX 10 NX
|
|
||||||
set, err := client.SetNX("key", "value", 10*time.Second).Result()
|
|
||||||
|
|
||||||
// SORT list LIMIT 0 2 ASC
|
|
||||||
vals, err := client.Sort("list", redis.Sort{Offset: 0, Count: 2, Order: "ASC"}).Result()
|
|
||||||
|
|
||||||
// ZRANGEBYSCORE zset -inf +inf WITHSCORES LIMIT 0 2
|
|
||||||
vals, err := client.ZRangeByScoreWithScores("zset", redis.ZRangeBy{
|
|
||||||
Min: "-inf",
|
|
||||||
Max: "+inf",
|
|
||||||
Offset: 0,
|
|
||||||
Count: 2,
|
|
||||||
}).Result()
|
|
||||||
|
|
||||||
// ZINTERSTORE out 2 zset1 zset2 WEIGHTS 2 3 AGGREGATE SUM
|
|
||||||
vals, err := client.ZInterStore("out", redis.ZStore{Weights: []int64{2, 3}}, "zset1", "zset2").Result()
|
|
||||||
|
|
||||||
// EVAL "return {KEYS[1],ARGV[1]}" 1 "key" "hello"
|
|
||||||
vals, err := client.Eval("return {KEYS[1],ARGV[1]}", []string{"key"}, "hello").Result()
|
|
||||||
```
|
|
||||||
|
|
||||||
## Benchmark
|
|
||||||
|
|
||||||
go-redis vs redigo:
|
|
||||||
|
|
||||||
```
|
|
||||||
BenchmarkSetGoRedis10Conns64Bytes-4 200000 7621 ns/op 210 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis100Conns64Bytes-4 200000 7554 ns/op 210 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis10Conns1KB-4 200000 7697 ns/op 210 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis100Conns1KB-4 200000 7688 ns/op 210 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis10Conns10KB-4 200000 9214 ns/op 210 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis100Conns10KB-4 200000 9181 ns/op 210 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis10Conns1MB-4 2000 583242 ns/op 2337 B/op 6 allocs/op
|
|
||||||
BenchmarkSetGoRedis100Conns1MB-4 2000 583089 ns/op 2338 B/op 6 allocs/op
|
|
||||||
BenchmarkSetRedigo10Conns64Bytes-4 200000 7576 ns/op 208 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo100Conns64Bytes-4 200000 7782 ns/op 208 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo10Conns1KB-4 200000 7958 ns/op 208 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo100Conns1KB-4 200000 7725 ns/op 208 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo10Conns10KB-4 100000 18442 ns/op 208 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo100Conns10KB-4 100000 18818 ns/op 208 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo10Conns1MB-4 2000 668829 ns/op 226 B/op 7 allocs/op
|
|
||||||
BenchmarkSetRedigo100Conns1MB-4 2000 679542 ns/op 226 B/op 7 allocs/op
|
|
||||||
```
|
|
||||||
|
|
||||||
Redis Cluster:
|
|
||||||
|
|
||||||
```
|
|
||||||
BenchmarkRedisPing-4 200000 6983 ns/op 116 B/op 4 allocs/op
|
|
||||||
BenchmarkRedisClusterPing-4 100000 11535 ns/op 117 B/op 4 allocs/op
|
|
||||||
```
|
|
||||||
|
|
||||||
## See also
|
|
||||||
|
|
||||||
- [Golang PostgreSQL ORM](https://github.com/go-pg/pg)
|
|
||||||
- [Golang msgpack](https://github.com/vmihailenco/msgpack)
|
|
||||||
- [Golang message task queue](https://github.com/vmihailenco/taskq)
|
|
||||||
1627
vendor/github.com/go-redis/redis/cluster.go
сгенерированный
поставляемый
1627
vendor/github.com/go-redis/redis/cluster.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
22
vendor/github.com/go-redis/redis/cluster_commands.go
сгенерированный
поставляемый
22
vendor/github.com/go-redis/redis/cluster_commands.go
сгенерированный
поставляемый
@@ -1,22 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import "sync/atomic"
|
|
||||||
|
|
||||||
func (c *ClusterClient) DBSize() *IntCmd {
|
|
||||||
cmd := NewIntCmd("dbsize")
|
|
||||||
var size int64
|
|
||||||
err := c.ForEachMaster(func(master *Client) error {
|
|
||||||
n, err := master.DBSize().Result()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
atomic.AddInt64(&size, n)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
cmd.setErr(err)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
cmd.val = size
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
1966
vendor/github.com/go-redis/redis/command.go
сгенерированный
поставляемый
1966
vendor/github.com/go-redis/redis/command.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
2583
vendor/github.com/go-redis/redis/commands.go
сгенерированный
поставляемый
2583
vendor/github.com/go-redis/redis/commands.go
сгенерированный
поставляемый
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
4
vendor/github.com/go-redis/redis/doc.go
сгенерированный
поставляемый
4
vendor/github.com/go-redis/redis/doc.go
сгенерированный
поставляемый
@@ -1,4 +0,0 @@
|
|||||||
/*
|
|
||||||
Package redis implements a Redis client.
|
|
||||||
*/
|
|
||||||
package redis
|
|
||||||
81
vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go
сгенерированный
поставляемый
81
vendor/github.com/go-redis/redis/internal/consistenthash/consistenthash.go
сгенерированный
поставляемый
@@ -1,81 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2013 Google Inc.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
// Package consistenthash provides an implementation of a ring hash.
|
|
||||||
package consistenthash
|
|
||||||
|
|
||||||
import (
|
|
||||||
"hash/crc32"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Hash func(data []byte) uint32
|
|
||||||
|
|
||||||
type Map struct {
|
|
||||||
hash Hash
|
|
||||||
replicas int
|
|
||||||
keys []int // Sorted
|
|
||||||
hashMap map[int]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func New(replicas int, fn Hash) *Map {
|
|
||||||
m := &Map{
|
|
||||||
replicas: replicas,
|
|
||||||
hash: fn,
|
|
||||||
hashMap: make(map[int]string),
|
|
||||||
}
|
|
||||||
if m.hash == nil {
|
|
||||||
m.hash = crc32.ChecksumIEEE
|
|
||||||
}
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
// Returns true if there are no items available.
|
|
||||||
func (m *Map) IsEmpty() bool {
|
|
||||||
return len(m.keys) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
// Adds some keys to the hash.
|
|
||||||
func (m *Map) Add(keys ...string) {
|
|
||||||
for _, key := range keys {
|
|
||||||
for i := 0; i < m.replicas; i++ {
|
|
||||||
hash := int(m.hash([]byte(strconv.Itoa(i) + key)))
|
|
||||||
m.keys = append(m.keys, hash)
|
|
||||||
m.hashMap[hash] = key
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sort.Ints(m.keys)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gets the closest item in the hash to the provided key.
|
|
||||||
func (m *Map) Get(key string) string {
|
|
||||||
if m.IsEmpty() {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := int(m.hash([]byte(key)))
|
|
||||||
|
|
||||||
// Binary search for appropriate replica.
|
|
||||||
idx := sort.Search(len(m.keys), func(i int) bool { return m.keys[i] >= hash })
|
|
||||||
|
|
||||||
// Means we have cycled back to the first replica.
|
|
||||||
if idx == len(m.keys) {
|
|
||||||
idx = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
return m.hashMap[m.keys[idx]]
|
|
||||||
}
|
|
||||||
89
vendor/github.com/go-redis/redis/internal/error.go
сгенерированный
поставляемый
89
vendor/github.com/go-redis/redis/internal/error.go
сгенерированный
поставляемый
@@ -1,89 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"io"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
func IsRetryableError(err error, retryTimeout bool) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if err == io.EOF {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if netErr, ok := err.(net.Error); ok {
|
|
||||||
if netErr.Timeout() {
|
|
||||||
return retryTimeout
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
s := err.Error()
|
|
||||||
if s == "ERR max number of clients reached" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(s, "LOADING ") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(s, "READONLY ") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if strings.HasPrefix(s, "CLUSTERDOWN ") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsRedisError(err error) bool {
|
|
||||||
_, ok := err.(proto.RedisError)
|
|
||||||
return ok
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsBadConn(err error, allowTimeout bool) bool {
|
|
||||||
if err == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if IsRedisError(err) {
|
|
||||||
// #790
|
|
||||||
return IsReadOnlyError(err)
|
|
||||||
}
|
|
||||||
if allowTimeout {
|
|
||||||
if netErr, ok := err.(net.Error); ok && netErr.Timeout() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsMovedError(err error) (moved bool, ask bool, addr string) {
|
|
||||||
if !IsRedisError(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
s := err.Error()
|
|
||||||
if strings.HasPrefix(s, "MOVED ") {
|
|
||||||
moved = true
|
|
||||||
} else if strings.HasPrefix(s, "ASK ") {
|
|
||||||
ask = true
|
|
||||||
} else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
ind := strings.LastIndex(s, " ")
|
|
||||||
if ind == -1 {
|
|
||||||
return false, false, ""
|
|
||||||
}
|
|
||||||
addr = s[ind+1:]
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsLoadingError(err error) bool {
|
|
||||||
return strings.HasPrefix(err.Error(), "LOADING ")
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsReadOnlyError(err error) bool {
|
|
||||||
return strings.HasPrefix(err.Error(), "READONLY ")
|
|
||||||
}
|
|
||||||
77
vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go
сгенерированный
поставляемый
77
vendor/github.com/go-redis/redis/internal/hashtag/hashtag.go
сгенерированный
поставляемый
@@ -1,77 +0,0 @@
|
|||||||
package hashtag
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
const slotNumber = 16384
|
|
||||||
|
|
||||||
// CRC16 implementation according to CCITT standards.
|
|
||||||
// Copyright 2001-2010 Georges Menie (www.menie.org)
|
|
||||||
// Copyright 2013 The Go Authors. All rights reserved.
|
|
||||||
// http://redis.io/topics/cluster-spec#appendix-a-crc16-reference-implementation-in-ansi-c
|
|
||||||
var crc16tab = [256]uint16{
|
|
||||||
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
|
||||||
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
|
||||||
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
|
||||||
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
|
||||||
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
|
||||||
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
|
||||||
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
|
||||||
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
|
||||||
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
|
||||||
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
|
||||||
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
|
||||||
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
|
||||||
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
|
||||||
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
|
||||||
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
|
||||||
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
|
||||||
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
|
||||||
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
|
||||||
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
|
||||||
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
|
||||||
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
|
||||||
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
|
||||||
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
|
||||||
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
|
||||||
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
|
||||||
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
|
||||||
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
|
||||||
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
|
||||||
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
|
||||||
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
|
||||||
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
|
||||||
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0,
|
|
||||||
}
|
|
||||||
|
|
||||||
func Key(key string) string {
|
|
||||||
if s := strings.IndexByte(key, '{'); s > -1 {
|
|
||||||
if e := strings.IndexByte(key[s+1:], '}'); e > 0 {
|
|
||||||
return key[s+1 : s+e+1]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return key
|
|
||||||
}
|
|
||||||
|
|
||||||
func RandomSlot() int {
|
|
||||||
return rand.Intn(slotNumber)
|
|
||||||
}
|
|
||||||
|
|
||||||
// hashSlot returns a consistent slot number between 0 and 16383
|
|
||||||
// for any given string key.
|
|
||||||
func Slot(key string) int {
|
|
||||||
if key == "" {
|
|
||||||
return RandomSlot()
|
|
||||||
}
|
|
||||||
key = Key(key)
|
|
||||||
return int(crc16sum(key)) % slotNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
func crc16sum(key string) (crc uint16) {
|
|
||||||
for i := 0; i < len(key); i++ {
|
|
||||||
crc = (crc << 8) ^ crc16tab[(byte(crc>>8)^key[i])&0x00ff]
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
24
vendor/github.com/go-redis/redis/internal/internal.go
сгенерированный
поставляемый
24
vendor/github.com/go-redis/redis/internal/internal.go
сгенерированный
поставляемый
@@ -1,24 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Retry backoff with jitter sleep to prevent overloaded conditions during intervals
|
|
||||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html
|
|
||||||
func RetryBackoff(retry int, minBackoff, maxBackoff time.Duration) time.Duration {
|
|
||||||
if retry < 0 {
|
|
||||||
retry = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
backoff := minBackoff << uint(retry)
|
|
||||||
if backoff > maxBackoff || backoff < minBackoff {
|
|
||||||
backoff = maxBackoff
|
|
||||||
}
|
|
||||||
|
|
||||||
if backoff == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return time.Duration(rand.Int63n(int64(backoff)))
|
|
||||||
}
|
|
||||||
15
vendor/github.com/go-redis/redis/internal/log.go
сгенерированный
поставляемый
15
vendor/github.com/go-redis/redis/internal/log.go
сгенерированный
поставляемый
@@ -1,15 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
)
|
|
||||||
|
|
||||||
var Logger *log.Logger
|
|
||||||
|
|
||||||
func Logf(s string, args ...interface{}) {
|
|
||||||
if Logger == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Logger.Output(2, fmt.Sprintf(s, args...))
|
|
||||||
}
|
|
||||||
60
vendor/github.com/go-redis/redis/internal/once.go
сгенерированный
поставляемый
60
vendor/github.com/go-redis/redis/internal/once.go
сгенерированный
поставляемый
@@ -1,60 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2014 The Camlistore Authors
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
// A Once will perform a successful action exactly once.
|
|
||||||
//
|
|
||||||
// Unlike a sync.Once, this Once's func returns an error
|
|
||||||
// and is re-armed on failure.
|
|
||||||
type Once struct {
|
|
||||||
m sync.Mutex
|
|
||||||
done uint32
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do calls the function f if and only if Do has not been invoked
|
|
||||||
// without error for this instance of Once. In other words, given
|
|
||||||
// var once Once
|
|
||||||
// if once.Do(f) is called multiple times, only the first call will
|
|
||||||
// invoke f, even if f has a different value in each invocation unless
|
|
||||||
// f returns an error. A new instance of Once is required for each
|
|
||||||
// function to execute.
|
|
||||||
//
|
|
||||||
// Do is intended for initialization that must be run exactly once. Since f
|
|
||||||
// is niladic, it may be necessary to use a function literal to capture the
|
|
||||||
// arguments to a function to be invoked by Do:
|
|
||||||
// err := config.once.Do(func() error { return config.init(filename) })
|
|
||||||
func (o *Once) Do(f func() error) error {
|
|
||||||
if atomic.LoadUint32(&o.done) == 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
// Slow-path.
|
|
||||||
o.m.Lock()
|
|
||||||
defer o.m.Unlock()
|
|
||||||
var err error
|
|
||||||
if o.done == 0 {
|
|
||||||
err = f()
|
|
||||||
if err == nil {
|
|
||||||
atomic.StoreUint32(&o.done, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
95
vendor/github.com/go-redis/redis/internal/pool/conn.go
сгенерированный
поставляемый
95
vendor/github.com/go-redis/redis/internal/pool/conn.go
сгенерированный
поставляемый
@@ -1,95 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var noDeadline = time.Time{}
|
|
||||||
|
|
||||||
type Conn struct {
|
|
||||||
netConn net.Conn
|
|
||||||
|
|
||||||
rd *proto.Reader
|
|
||||||
rdLocked bool
|
|
||||||
wr *proto.Writer
|
|
||||||
|
|
||||||
Inited bool
|
|
||||||
pooled bool
|
|
||||||
createdAt time.Time
|
|
||||||
usedAt atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConn(netConn net.Conn) *Conn {
|
|
||||||
cn := &Conn{
|
|
||||||
netConn: netConn,
|
|
||||||
createdAt: time.Now(),
|
|
||||||
}
|
|
||||||
cn.rd = proto.NewReader(netConn)
|
|
||||||
cn.wr = proto.NewWriter(netConn)
|
|
||||||
cn.SetUsedAt(time.Now())
|
|
||||||
return cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) UsedAt() time.Time {
|
|
||||||
return cn.usedAt.Load().(time.Time)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) SetUsedAt(tm time.Time) {
|
|
||||||
cn.usedAt.Store(tm)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) SetNetConn(netConn net.Conn) {
|
|
||||||
cn.netConn = netConn
|
|
||||||
cn.rd.Reset(netConn)
|
|
||||||
cn.wr.Reset(netConn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) setReadTimeout(timeout time.Duration) error {
|
|
||||||
now := time.Now()
|
|
||||||
cn.SetUsedAt(now)
|
|
||||||
if timeout > 0 {
|
|
||||||
return cn.netConn.SetReadDeadline(now.Add(timeout))
|
|
||||||
}
|
|
||||||
return cn.netConn.SetReadDeadline(noDeadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) setWriteTimeout(timeout time.Duration) error {
|
|
||||||
now := time.Now()
|
|
||||||
cn.SetUsedAt(now)
|
|
||||||
if timeout > 0 {
|
|
||||||
return cn.netConn.SetWriteDeadline(now.Add(timeout))
|
|
||||||
}
|
|
||||||
return cn.netConn.SetWriteDeadline(noDeadline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) Write(b []byte) (int, error) {
|
|
||||||
return cn.netConn.Write(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) RemoteAddr() net.Addr {
|
|
||||||
return cn.netConn.RemoteAddr()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) WithReader(timeout time.Duration, fn func(rd *proto.Reader) error) error {
|
|
||||||
_ = cn.setReadTimeout(timeout)
|
|
||||||
return fn(cn.rd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) WithWriter(timeout time.Duration, fn func(wr *proto.Writer) error) error {
|
|
||||||
_ = cn.setWriteTimeout(timeout)
|
|
||||||
|
|
||||||
firstErr := fn(cn.wr)
|
|
||||||
err := cn.wr.Flush()
|
|
||||||
if err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cn *Conn) Close() error {
|
|
||||||
return cn.netConn.Close()
|
|
||||||
}
|
|
||||||
476
vendor/github.com/go-redis/redis/internal/pool/pool.go
сгенерированный
поставляемый
476
vendor/github.com/go-redis/redis/internal/pool/pool.go
сгенерированный
поставляемый
@@ -1,476 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
var ErrClosed = errors.New("redis: client is closed")
|
|
||||||
var ErrPoolTimeout = errors.New("redis: connection pool timeout")
|
|
||||||
|
|
||||||
var timers = sync.Pool{
|
|
||||||
New: func() interface{} {
|
|
||||||
t := time.NewTimer(time.Hour)
|
|
||||||
t.Stop()
|
|
||||||
return t
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Stats contains pool state information and accumulated stats.
|
|
||||||
type Stats struct {
|
|
||||||
Hits uint32 // number of times free connection was found in the pool
|
|
||||||
Misses uint32 // number of times free connection was NOT found in the pool
|
|
||||||
Timeouts uint32 // number of times a wait timeout occurred
|
|
||||||
|
|
||||||
TotalConns uint32 // number of total connections in the pool
|
|
||||||
IdleConns uint32 // number of idle connections in the pool
|
|
||||||
StaleConns uint32 // number of stale connections removed from the pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pooler interface {
|
|
||||||
NewConn() (*Conn, error)
|
|
||||||
CloseConn(*Conn) error
|
|
||||||
|
|
||||||
Get() (*Conn, error)
|
|
||||||
Put(*Conn)
|
|
||||||
Remove(*Conn, error)
|
|
||||||
|
|
||||||
Len() int
|
|
||||||
IdleLen() int
|
|
||||||
Stats() *Stats
|
|
||||||
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
Dialer func() (net.Conn, error)
|
|
||||||
OnClose func(*Conn) error
|
|
||||||
|
|
||||||
PoolSize int
|
|
||||||
MinIdleConns int
|
|
||||||
MaxConnAge time.Duration
|
|
||||||
PoolTimeout time.Duration
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
IdleCheckFrequency time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnPool struct {
|
|
||||||
opt *Options
|
|
||||||
|
|
||||||
dialErrorsNum uint32 // atomic
|
|
||||||
|
|
||||||
lastDialErrorMu sync.RWMutex
|
|
||||||
lastDialError error
|
|
||||||
|
|
||||||
queue chan struct{}
|
|
||||||
|
|
||||||
connsMu sync.Mutex
|
|
||||||
conns []*Conn
|
|
||||||
idleConns []*Conn
|
|
||||||
poolSize int
|
|
||||||
idleConnsLen int
|
|
||||||
|
|
||||||
stats Stats
|
|
||||||
|
|
||||||
_closed uint32 // atomic
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Pooler = (*ConnPool)(nil)
|
|
||||||
|
|
||||||
func NewConnPool(opt *Options) *ConnPool {
|
|
||||||
p := &ConnPool{
|
|
||||||
opt: opt,
|
|
||||||
|
|
||||||
queue: make(chan struct{}, opt.PoolSize),
|
|
||||||
conns: make([]*Conn, 0, opt.PoolSize),
|
|
||||||
idleConns: make([]*Conn, 0, opt.PoolSize),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < opt.MinIdleConns; i++ {
|
|
||||||
p.checkMinIdleConns()
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.IdleTimeout > 0 && opt.IdleCheckFrequency > 0 {
|
|
||||||
go p.reaper(opt.IdleCheckFrequency)
|
|
||||||
}
|
|
||||||
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) checkMinIdleConns() {
|
|
||||||
if p.opt.MinIdleConns == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if p.poolSize < p.opt.PoolSize && p.idleConnsLen < p.opt.MinIdleConns {
|
|
||||||
p.poolSize++
|
|
||||||
p.idleConnsLen++
|
|
||||||
go p.addIdleConn()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) addIdleConn() {
|
|
||||||
cn, err := p.newConn(true)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.connsMu.Lock()
|
|
||||||
p.conns = append(p.conns, cn)
|
|
||||||
p.idleConns = append(p.idleConns, cn)
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) NewConn() (*Conn, error) {
|
|
||||||
return p._NewConn(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) _NewConn(pooled bool) (*Conn, error) {
|
|
||||||
cn, err := p.newConn(pooled)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.connsMu.Lock()
|
|
||||||
p.conns = append(p.conns, cn)
|
|
||||||
if pooled {
|
|
||||||
if p.poolSize < p.opt.PoolSize {
|
|
||||||
p.poolSize++
|
|
||||||
} else {
|
|
||||||
cn.pooled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) newConn(pooled bool) (*Conn, error) {
|
|
||||||
if p.closed() {
|
|
||||||
return nil, ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
if atomic.LoadUint32(&p.dialErrorsNum) >= uint32(p.opt.PoolSize) {
|
|
||||||
return nil, p.getLastDialError()
|
|
||||||
}
|
|
||||||
|
|
||||||
netConn, err := p.opt.Dialer()
|
|
||||||
if err != nil {
|
|
||||||
p.setLastDialError(err)
|
|
||||||
if atomic.AddUint32(&p.dialErrorsNum, 1) == uint32(p.opt.PoolSize) {
|
|
||||||
go p.tryDial()
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := NewConn(netConn)
|
|
||||||
cn.pooled = pooled
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) tryDial() {
|
|
||||||
for {
|
|
||||||
if p.closed() {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, err := p.opt.Dialer()
|
|
||||||
if err != nil {
|
|
||||||
p.setLastDialError(err)
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.StoreUint32(&p.dialErrorsNum, 0)
|
|
||||||
_ = conn.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) setLastDialError(err error) {
|
|
||||||
p.lastDialErrorMu.Lock()
|
|
||||||
p.lastDialError = err
|
|
||||||
p.lastDialErrorMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) getLastDialError() error {
|
|
||||||
p.lastDialErrorMu.RLock()
|
|
||||||
err := p.lastDialError
|
|
||||||
p.lastDialErrorMu.RUnlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get returns existed connection from the pool or creates a new one.
|
|
||||||
func (p *ConnPool) Get() (*Conn, error) {
|
|
||||||
if p.closed() {
|
|
||||||
return nil, ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
err := p.waitTurn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
p.connsMu.Lock()
|
|
||||||
cn := p.popIdle()
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
|
|
||||||
if cn == nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if p.isStaleConn(cn) {
|
|
||||||
_ = p.CloseConn(cn)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddUint32(&p.stats.Hits, 1)
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddUint32(&p.stats.Misses, 1)
|
|
||||||
|
|
||||||
newcn, err := p._NewConn(true)
|
|
||||||
if err != nil {
|
|
||||||
p.freeTurn()
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return newcn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) getTurn() {
|
|
||||||
p.queue <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) waitTurn() error {
|
|
||||||
select {
|
|
||||||
case p.queue <- struct{}{}:
|
|
||||||
return nil
|
|
||||||
default:
|
|
||||||
timer := timers.Get().(*time.Timer)
|
|
||||||
timer.Reset(p.opt.PoolTimeout)
|
|
||||||
|
|
||||||
select {
|
|
||||||
case p.queue <- struct{}{}:
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
timers.Put(timer)
|
|
||||||
return nil
|
|
||||||
case <-timer.C:
|
|
||||||
timers.Put(timer)
|
|
||||||
atomic.AddUint32(&p.stats.Timeouts, 1)
|
|
||||||
return ErrPoolTimeout
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) freeTurn() {
|
|
||||||
<-p.queue
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) popIdle() *Conn {
|
|
||||||
if len(p.idleConns) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
idx := len(p.idleConns) - 1
|
|
||||||
cn := p.idleConns[idx]
|
|
||||||
p.idleConns = p.idleConns[:idx]
|
|
||||||
p.idleConnsLen--
|
|
||||||
p.checkMinIdleConns()
|
|
||||||
return cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Put(cn *Conn) {
|
|
||||||
if !cn.pooled {
|
|
||||||
p.Remove(cn, nil)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
p.connsMu.Lock()
|
|
||||||
p.idleConns = append(p.idleConns, cn)
|
|
||||||
p.idleConnsLen++
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
p.freeTurn()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Remove(cn *Conn, reason error) {
|
|
||||||
p.removeConn(cn)
|
|
||||||
p.freeTurn()
|
|
||||||
_ = p.closeConn(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) CloseConn(cn *Conn) error {
|
|
||||||
p.removeConn(cn)
|
|
||||||
return p.closeConn(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) removeConn(cn *Conn) {
|
|
||||||
p.connsMu.Lock()
|
|
||||||
for i, c := range p.conns {
|
|
||||||
if c == cn {
|
|
||||||
p.conns = append(p.conns[:i], p.conns[i+1:]...)
|
|
||||||
if cn.pooled {
|
|
||||||
p.poolSize--
|
|
||||||
p.checkMinIdleConns()
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) closeConn(cn *Conn) error {
|
|
||||||
if p.opt.OnClose != nil {
|
|
||||||
_ = p.opt.OnClose(cn)
|
|
||||||
}
|
|
||||||
return cn.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns total number of connections.
|
|
||||||
func (p *ConnPool) Len() int {
|
|
||||||
p.connsMu.Lock()
|
|
||||||
n := len(p.conns)
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
// IdleLen returns number of idle connections.
|
|
||||||
func (p *ConnPool) IdleLen() int {
|
|
||||||
p.connsMu.Lock()
|
|
||||||
n := p.idleConnsLen
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
return n
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Stats() *Stats {
|
|
||||||
idleLen := p.IdleLen()
|
|
||||||
return &Stats{
|
|
||||||
Hits: atomic.LoadUint32(&p.stats.Hits),
|
|
||||||
Misses: atomic.LoadUint32(&p.stats.Misses),
|
|
||||||
Timeouts: atomic.LoadUint32(&p.stats.Timeouts),
|
|
||||||
|
|
||||||
TotalConns: uint32(p.Len()),
|
|
||||||
IdleConns: uint32(idleLen),
|
|
||||||
StaleConns: atomic.LoadUint32(&p.stats.StaleConns),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) closed() bool {
|
|
||||||
return atomic.LoadUint32(&p._closed) == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Filter(fn func(*Conn) bool) error {
|
|
||||||
var firstErr error
|
|
||||||
p.connsMu.Lock()
|
|
||||||
for _, cn := range p.conns {
|
|
||||||
if fn(cn) {
|
|
||||||
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) Close() error {
|
|
||||||
if !atomic.CompareAndSwapUint32(&p._closed, 0, 1) {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
var firstErr error
|
|
||||||
p.connsMu.Lock()
|
|
||||||
for _, cn := range p.conns {
|
|
||||||
if err := p.closeConn(cn); err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
p.conns = nil
|
|
||||||
p.poolSize = 0
|
|
||||||
p.idleConns = nil
|
|
||||||
p.idleConnsLen = 0
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) reapStaleConn() *Conn {
|
|
||||||
if len(p.idleConns) == 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cn := p.idleConns[0]
|
|
||||||
if !p.isStaleConn(cn) {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
p.idleConns = append(p.idleConns[:0], p.idleConns[1:]...)
|
|
||||||
p.idleConnsLen--
|
|
||||||
|
|
||||||
return cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) ReapStaleConns() (int, error) {
|
|
||||||
var n int
|
|
||||||
for {
|
|
||||||
p.getTurn()
|
|
||||||
|
|
||||||
p.connsMu.Lock()
|
|
||||||
cn := p.reapStaleConn()
|
|
||||||
p.connsMu.Unlock()
|
|
||||||
|
|
||||||
if cn != nil {
|
|
||||||
p.removeConn(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.freeTurn()
|
|
||||||
|
|
||||||
if cn != nil {
|
|
||||||
p.closeConn(cn)
|
|
||||||
n++
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return n, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) reaper(frequency time.Duration) {
|
|
||||||
ticker := time.NewTicker(frequency)
|
|
||||||
defer ticker.Stop()
|
|
||||||
|
|
||||||
for range ticker.C {
|
|
||||||
if p.closed() {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
n, err := p.ReapStaleConns()
|
|
||||||
if err != nil {
|
|
||||||
internal.Logf("ReapStaleConns failed: %s", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
atomic.AddUint32(&p.stats.StaleConns, uint32(n))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *ConnPool) isStaleConn(cn *Conn) bool {
|
|
||||||
if p.opt.IdleTimeout == 0 && p.opt.MaxConnAge == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
if p.opt.IdleTimeout > 0 && now.Sub(cn.UsedAt()) >= p.opt.IdleTimeout {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if p.opt.MaxConnAge > 0 && now.Sub(cn.createdAt) >= p.opt.MaxConnAge {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
203
vendor/github.com/go-redis/redis/internal/pool/pool_single.go
сгенерированный
поставляемый
203
vendor/github.com/go-redis/redis/internal/pool/pool_single.go
сгенерированный
поставляемый
@@ -1,203 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sync/atomic"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
stateDefault = 0
|
|
||||||
stateInited = 1
|
|
||||||
stateClosed = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type BadConnError struct {
|
|
||||||
wrapped error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ error = (*BadConnError)(nil)
|
|
||||||
|
|
||||||
func (e BadConnError) Error() string {
|
|
||||||
return "pg: Conn is in a bad state"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (e BadConnError) Unwrap() error {
|
|
||||||
return e.wrapped
|
|
||||||
}
|
|
||||||
|
|
||||||
type SingleConnPool struct {
|
|
||||||
pool Pooler
|
|
||||||
level int32 // atomic
|
|
||||||
|
|
||||||
state uint32 // atomic
|
|
||||||
ch chan *Conn
|
|
||||||
|
|
||||||
_badConnError atomic.Value
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Pooler = (*SingleConnPool)(nil)
|
|
||||||
|
|
||||||
func NewSingleConnPool(pool Pooler) *SingleConnPool {
|
|
||||||
p, ok := pool.(*SingleConnPool)
|
|
||||||
if !ok {
|
|
||||||
p = &SingleConnPool{
|
|
||||||
pool: pool,
|
|
||||||
ch: make(chan *Conn, 1),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
atomic.AddInt32(&p.level, 1)
|
|
||||||
return p
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) SetConn(cn *Conn) {
|
|
||||||
if atomic.CompareAndSwapUint32(&p.state, stateDefault, stateInited) {
|
|
||||||
p.ch <- cn
|
|
||||||
} else {
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) NewConn() (*Conn, error) {
|
|
||||||
return p.pool.NewConn()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) CloseConn(cn *Conn) error {
|
|
||||||
return p.pool.CloseConn(cn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Get() (*Conn, error) {
|
|
||||||
// In worst case this races with Close which is not a very common operation.
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
switch atomic.LoadUint32(&p.state) {
|
|
||||||
case stateDefault:
|
|
||||||
cn, err := p.pool.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if atomic.CompareAndSwapUint32(&p.state, stateDefault, stateInited) {
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
p.pool.Remove(cn, ErrClosed)
|
|
||||||
case stateInited:
|
|
||||||
if err := p.badConnError(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cn, ok := <-p.ch
|
|
||||||
if !ok {
|
|
||||||
return nil, ErrClosed
|
|
||||||
}
|
|
||||||
return cn, nil
|
|
||||||
case stateClosed:
|
|
||||||
return nil, ErrClosed
|
|
||||||
default:
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("pg: SingleConnPool.Get: infinite loop")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Put(cn *Conn) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
p.freeConn(cn)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
p.ch <- cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) freeConn(cn *Conn) {
|
|
||||||
if err := p.badConnError(); err != nil {
|
|
||||||
p.pool.Remove(cn, err)
|
|
||||||
} else {
|
|
||||||
p.pool.Put(cn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Remove(cn *Conn, reason error) {
|
|
||||||
defer func() {
|
|
||||||
if recover() != nil {
|
|
||||||
p.pool.Remove(cn, ErrClosed)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
p._badConnError.Store(BadConnError{wrapped: reason})
|
|
||||||
p.ch <- cn
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Len() int {
|
|
||||||
switch atomic.LoadUint32(&p.state) {
|
|
||||||
case stateDefault:
|
|
||||||
return 0
|
|
||||||
case stateInited:
|
|
||||||
return 1
|
|
||||||
case stateClosed:
|
|
||||||
return 0
|
|
||||||
default:
|
|
||||||
panic("not reached")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) IdleLen() int {
|
|
||||||
return len(p.ch)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Stats() *Stats {
|
|
||||||
return &Stats{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Close() error {
|
|
||||||
level := atomic.AddInt32(&p.level, -1)
|
|
||||||
if level > 0 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
|
||||||
state := atomic.LoadUint32(&p.state)
|
|
||||||
if state == stateClosed {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
if atomic.CompareAndSwapUint32(&p.state, state, stateClosed) {
|
|
||||||
close(p.ch)
|
|
||||||
cn, ok := <-p.ch
|
|
||||||
if ok {
|
|
||||||
p.freeConn(cn)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return fmt.Errorf("pg: SingleConnPool.Close: infinite loop")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) Reset() error {
|
|
||||||
if p.badConnError() == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
select {
|
|
||||||
case cn, ok := <-p.ch:
|
|
||||||
if !ok {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
p.pool.Remove(cn, ErrClosed)
|
|
||||||
p._badConnError.Store(BadConnError{wrapped: nil})
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("pg: SingleConnPool does not have a Conn")
|
|
||||||
}
|
|
||||||
|
|
||||||
if !atomic.CompareAndSwapUint32(&p.state, stateInited, stateDefault) {
|
|
||||||
state := atomic.LoadUint32(&p.state)
|
|
||||||
return fmt.Errorf("pg: invalid SingleConnPool state: %d", state)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *SingleConnPool) badConnError() error {
|
|
||||||
if v := p._badConnError.Load(); v != nil {
|
|
||||||
err := v.(BadConnError)
|
|
||||||
if err.wrapped != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
109
vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go
сгенерированный
поставляемый
109
vendor/github.com/go-redis/redis/internal/pool/pool_sticky.go
сгенерированный
поставляемый
@@ -1,109 +0,0 @@
|
|||||||
package pool
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
type StickyConnPool struct {
|
|
||||||
pool *ConnPool
|
|
||||||
reusable bool
|
|
||||||
|
|
||||||
cn *Conn
|
|
||||||
closed bool
|
|
||||||
mu sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Pooler = (*StickyConnPool)(nil)
|
|
||||||
|
|
||||||
func NewStickyConnPool(pool *ConnPool, reusable bool) *StickyConnPool {
|
|
||||||
return &StickyConnPool{
|
|
||||||
pool: pool,
|
|
||||||
reusable: reusable,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) NewConn() (*Conn, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) CloseConn(*Conn) error {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) Get() (*Conn, error) {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
if p.closed {
|
|
||||||
return nil, ErrClosed
|
|
||||||
}
|
|
||||||
if p.cn != nil {
|
|
||||||
return p.cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := p.pool.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
p.cn = cn
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) putUpstream() {
|
|
||||||
p.pool.Put(p.cn)
|
|
||||||
p.cn = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) Put(cn *Conn) {}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) removeUpstream(reason error) {
|
|
||||||
p.pool.Remove(p.cn, reason)
|
|
||||||
p.cn = nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) Remove(cn *Conn, reason error) {
|
|
||||||
p.removeUpstream(reason)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) Len() int {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
if p.cn == nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) IdleLen() int {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
if p.cn == nil {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) Stats() *Stats {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *StickyConnPool) Close() error {
|
|
||||||
p.mu.Lock()
|
|
||||||
defer p.mu.Unlock()
|
|
||||||
|
|
||||||
if p.closed {
|
|
||||||
return ErrClosed
|
|
||||||
}
|
|
||||||
p.closed = true
|
|
||||||
|
|
||||||
if p.cn != nil {
|
|
||||||
if p.reusable {
|
|
||||||
p.putUpstream()
|
|
||||||
} else {
|
|
||||||
p.removeUpstream(ErrClosed)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
290
vendor/github.com/go-redis/redis/internal/proto/reader.go
сгенерированный
поставляемый
290
vendor/github.com/go-redis/redis/internal/proto/reader.go
сгенерированный
поставляемый
@@ -1,290 +0,0 @@
|
|||||||
package proto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
ErrorReply = '-'
|
|
||||||
StatusReply = '+'
|
|
||||||
IntReply = ':'
|
|
||||||
StringReply = '$'
|
|
||||||
ArrayReply = '*'
|
|
||||||
)
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
const Nil = RedisError("redis: nil")
|
|
||||||
|
|
||||||
type RedisError string
|
|
||||||
|
|
||||||
func (e RedisError) Error() string { return string(e) }
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type MultiBulkParse func(*Reader, int64) (interface{}, error)
|
|
||||||
|
|
||||||
type Reader struct {
|
|
||||||
rd *bufio.Reader
|
|
||||||
_buf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewReader(rd io.Reader) *Reader {
|
|
||||||
return &Reader{
|
|
||||||
rd: bufio.NewReader(rd),
|
|
||||||
_buf: make([]byte, 64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) Reset(rd io.Reader) {
|
|
||||||
r.rd.Reset(rd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadLine() ([]byte, error) {
|
|
||||||
line, isPrefix, err := r.rd.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if isPrefix {
|
|
||||||
return nil, bufio.ErrBufferFull
|
|
||||||
}
|
|
||||||
if len(line) == 0 {
|
|
||||||
return nil, fmt.Errorf("redis: reply is empty")
|
|
||||||
}
|
|
||||||
if isNilReply(line) {
|
|
||||||
return nil, Nil
|
|
||||||
}
|
|
||||||
return line, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadReply(m MultiBulkParse) (interface{}, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch line[0] {
|
|
||||||
case ErrorReply:
|
|
||||||
return nil, ParseErrorReply(line)
|
|
||||||
case StatusReply:
|
|
||||||
return string(line[1:]), nil
|
|
||||||
case IntReply:
|
|
||||||
return util.ParseInt(line[1:], 10, 64)
|
|
||||||
case StringReply:
|
|
||||||
return r.readStringReply(line)
|
|
||||||
case ArrayReply:
|
|
||||||
n, err := parseArrayLen(line)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m(r, n)
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("redis: can't parse %.100q", line)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadIntReply() (int64, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch line[0] {
|
|
||||||
case ErrorReply:
|
|
||||||
return 0, ParseErrorReply(line)
|
|
||||||
case IntReply:
|
|
||||||
return util.ParseInt(line[1:], 10, 64)
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("redis: can't parse int reply: %.100q", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadString() (string, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
switch line[0] {
|
|
||||||
case ErrorReply:
|
|
||||||
return "", ParseErrorReply(line)
|
|
||||||
case StringReply:
|
|
||||||
return r.readStringReply(line)
|
|
||||||
case StatusReply:
|
|
||||||
return string(line[1:]), nil
|
|
||||||
case IntReply:
|
|
||||||
return string(line[1:]), nil
|
|
||||||
default:
|
|
||||||
return "", fmt.Errorf("redis: can't parse reply=%.100q reading string", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) readStringReply(line []byte) (string, error) {
|
|
||||||
if isNilReply(line) {
|
|
||||||
return "", Nil
|
|
||||||
}
|
|
||||||
|
|
||||||
replyLen, err := strconv.Atoi(string(line[1:]))
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, replyLen+2)
|
|
||||||
_, err = io.ReadFull(r.rd, b)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return util.BytesToString(b[:replyLen]), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadArrayReply(m MultiBulkParse) (interface{}, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch line[0] {
|
|
||||||
case ErrorReply:
|
|
||||||
return nil, ParseErrorReply(line)
|
|
||||||
case ArrayReply:
|
|
||||||
n, err := parseArrayLen(line)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return m(r, n)
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("redis: can't parse array reply: %.100q", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadArrayLen() (int64, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
switch line[0] {
|
|
||||||
case ErrorReply:
|
|
||||||
return 0, ParseErrorReply(line)
|
|
||||||
case ArrayReply:
|
|
||||||
return parseArrayLen(line)
|
|
||||||
default:
|
|
||||||
return 0, fmt.Errorf("redis: can't parse array reply: %.100q", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadScanReply() ([]string, uint64, error) {
|
|
||||||
n, err := r.ReadArrayLen()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
if n != 2 {
|
|
||||||
return nil, 0, fmt.Errorf("redis: got %d elements in scan reply, expected 2", n)
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor, err := r.ReadUint()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
n, err = r.ReadArrayLen()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
keys := make([]string, n)
|
|
||||||
for i := int64(0); i < n; i++ {
|
|
||||||
key, err := r.ReadString()
|
|
||||||
if err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
keys[i] = key
|
|
||||||
}
|
|
||||||
|
|
||||||
return keys, cursor, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadInt() (int64, error) {
|
|
||||||
b, err := r.readTmpBytesReply()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return util.ParseInt(b, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadUint() (uint64, error) {
|
|
||||||
b, err := r.readTmpBytesReply()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return util.ParseUint(b, 10, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) ReadFloatReply() (float64, error) {
|
|
||||||
b, err := r.readTmpBytesReply()
|
|
||||||
if err != nil {
|
|
||||||
return 0, err
|
|
||||||
}
|
|
||||||
return util.ParseFloat(b, 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) readTmpBytesReply() ([]byte, error) {
|
|
||||||
line, err := r.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
switch line[0] {
|
|
||||||
case ErrorReply:
|
|
||||||
return nil, ParseErrorReply(line)
|
|
||||||
case StringReply:
|
|
||||||
return r._readTmpBytesReply(line)
|
|
||||||
case StatusReply:
|
|
||||||
return line[1:], nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("redis: can't parse string reply: %.100q", line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) _readTmpBytesReply(line []byte) ([]byte, error) {
|
|
||||||
if isNilReply(line) {
|
|
||||||
return nil, Nil
|
|
||||||
}
|
|
||||||
|
|
||||||
replyLen, err := strconv.Atoi(string(line[1:]))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
buf := r.buf(replyLen + 2)
|
|
||||||
_, err = io.ReadFull(r.rd, buf)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return buf[:replyLen], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *Reader) buf(n int) []byte {
|
|
||||||
if d := n - cap(r._buf); d > 0 {
|
|
||||||
r._buf = append(r._buf, make([]byte, d)...)
|
|
||||||
}
|
|
||||||
return r._buf[:n]
|
|
||||||
}
|
|
||||||
|
|
||||||
func isNilReply(b []byte) bool {
|
|
||||||
return len(b) == 3 &&
|
|
||||||
(b[0] == StringReply || b[0] == ArrayReply) &&
|
|
||||||
b[1] == '-' && b[2] == '1'
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseErrorReply(line []byte) error {
|
|
||||||
return RedisError(string(line[1:]))
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseArrayLen(line []byte) (int64, error) {
|
|
||||||
if isNilReply(line) {
|
|
||||||
return 0, Nil
|
|
||||||
}
|
|
||||||
return util.ParseInt(line[1:], 10, 64)
|
|
||||||
}
|
|
||||||
166
vendor/github.com/go-redis/redis/internal/proto/scan.go
сгенерированный
поставляемый
166
vendor/github.com/go-redis/redis/internal/proto/scan.go
сгенерированный
поставляемый
@@ -1,166 +0,0 @@
|
|||||||
package proto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding"
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Scan(b []byte, v interface{}) error {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case nil:
|
|
||||||
return fmt.Errorf("redis: Scan(nil)")
|
|
||||||
case *string:
|
|
||||||
*v = util.BytesToString(b)
|
|
||||||
return nil
|
|
||||||
case *[]byte:
|
|
||||||
*v = b
|
|
||||||
return nil
|
|
||||||
case *int:
|
|
||||||
var err error
|
|
||||||
*v, err = util.Atoi(b)
|
|
||||||
return err
|
|
||||||
case *int8:
|
|
||||||
n, err := util.ParseInt(b, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = int8(n)
|
|
||||||
return nil
|
|
||||||
case *int16:
|
|
||||||
n, err := util.ParseInt(b, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = int16(n)
|
|
||||||
return nil
|
|
||||||
case *int32:
|
|
||||||
n, err := util.ParseInt(b, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = int32(n)
|
|
||||||
return nil
|
|
||||||
case *int64:
|
|
||||||
n, err := util.ParseInt(b, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = n
|
|
||||||
return nil
|
|
||||||
case *uint:
|
|
||||||
n, err := util.ParseUint(b, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = uint(n)
|
|
||||||
return nil
|
|
||||||
case *uint8:
|
|
||||||
n, err := util.ParseUint(b, 10, 8)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = uint8(n)
|
|
||||||
return nil
|
|
||||||
case *uint16:
|
|
||||||
n, err := util.ParseUint(b, 10, 16)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = uint16(n)
|
|
||||||
return nil
|
|
||||||
case *uint32:
|
|
||||||
n, err := util.ParseUint(b, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = uint32(n)
|
|
||||||
return nil
|
|
||||||
case *uint64:
|
|
||||||
n, err := util.ParseUint(b, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = n
|
|
||||||
return nil
|
|
||||||
case *float32:
|
|
||||||
n, err := util.ParseFloat(b, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
*v = float32(n)
|
|
||||||
return err
|
|
||||||
case *float64:
|
|
||||||
var err error
|
|
||||||
*v, err = util.ParseFloat(b, 64)
|
|
||||||
return err
|
|
||||||
case *bool:
|
|
||||||
*v = len(b) == 1 && b[0] == '1'
|
|
||||||
return nil
|
|
||||||
case encoding.BinaryUnmarshaler:
|
|
||||||
return v.UnmarshalBinary(b)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf(
|
|
||||||
"redis: can't unmarshal %T (consider implementing BinaryUnmarshaler)", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func ScanSlice(data []string, slice interface{}) error {
|
|
||||||
v := reflect.ValueOf(slice)
|
|
||||||
if !v.IsValid() {
|
|
||||||
return fmt.Errorf("redis: ScanSlice(nil)")
|
|
||||||
}
|
|
||||||
if v.Kind() != reflect.Ptr {
|
|
||||||
return fmt.Errorf("redis: ScanSlice(non-pointer %T)", slice)
|
|
||||||
}
|
|
||||||
v = v.Elem()
|
|
||||||
if v.Kind() != reflect.Slice {
|
|
||||||
return fmt.Errorf("redis: ScanSlice(non-slice %T)", slice)
|
|
||||||
}
|
|
||||||
|
|
||||||
next := makeSliceNextElemFunc(v)
|
|
||||||
for i, s := range data {
|
|
||||||
elem := next()
|
|
||||||
if err := Scan([]byte(s), elem.Addr().Interface()); err != nil {
|
|
||||||
err = fmt.Errorf("redis: ScanSlice index=%d value=%q failed: %s", i, s, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeSliceNextElemFunc(v reflect.Value) func() reflect.Value {
|
|
||||||
elemType := v.Type().Elem()
|
|
||||||
|
|
||||||
if elemType.Kind() == reflect.Ptr {
|
|
||||||
elemType = elemType.Elem()
|
|
||||||
return func() reflect.Value {
|
|
||||||
if v.Len() < v.Cap() {
|
|
||||||
v.Set(v.Slice(0, v.Len()+1))
|
|
||||||
elem := v.Index(v.Len() - 1)
|
|
||||||
if elem.IsNil() {
|
|
||||||
elem.Set(reflect.New(elemType))
|
|
||||||
}
|
|
||||||
return elem.Elem()
|
|
||||||
}
|
|
||||||
|
|
||||||
elem := reflect.New(elemType)
|
|
||||||
v.Set(reflect.Append(v, elem))
|
|
||||||
return elem.Elem()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
zero := reflect.Zero(elemType)
|
|
||||||
return func() reflect.Value {
|
|
||||||
if v.Len() < v.Cap() {
|
|
||||||
v.Set(v.Slice(0, v.Len()+1))
|
|
||||||
return v.Index(v.Len() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
v.Set(reflect.Append(v, zero))
|
|
||||||
return v.Index(v.Len() - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
159
vendor/github.com/go-redis/redis/internal/proto/writer.go
сгенерированный
поставляемый
159
vendor/github.com/go-redis/redis/internal/proto/writer.go
сгенерированный
поставляемый
@@ -1,159 +0,0 @@
|
|||||||
package proto
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"encoding"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/util"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Writer struct {
|
|
||||||
wr *bufio.Writer
|
|
||||||
|
|
||||||
lenBuf []byte
|
|
||||||
numBuf []byte
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWriter(wr io.Writer) *Writer {
|
|
||||||
return &Writer{
|
|
||||||
wr: bufio.NewWriter(wr),
|
|
||||||
|
|
||||||
lenBuf: make([]byte, 64),
|
|
||||||
numBuf: make([]byte, 64),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) WriteArgs(args []interface{}) error {
|
|
||||||
err := w.wr.WriteByte(ArrayReply)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.writeLen(len(args))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
err := w.writeArg(arg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeLen(n int) error {
|
|
||||||
w.lenBuf = strconv.AppendUint(w.lenBuf[:0], uint64(n), 10)
|
|
||||||
w.lenBuf = append(w.lenBuf, '\r', '\n')
|
|
||||||
_, err := w.wr.Write(w.lenBuf)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) writeArg(v interface{}) error {
|
|
||||||
switch v := v.(type) {
|
|
||||||
case nil:
|
|
||||||
return w.string("")
|
|
||||||
case string:
|
|
||||||
return w.string(v)
|
|
||||||
case []byte:
|
|
||||||
return w.bytes(v)
|
|
||||||
case int:
|
|
||||||
return w.int(int64(v))
|
|
||||||
case int8:
|
|
||||||
return w.int(int64(v))
|
|
||||||
case int16:
|
|
||||||
return w.int(int64(v))
|
|
||||||
case int32:
|
|
||||||
return w.int(int64(v))
|
|
||||||
case int64:
|
|
||||||
return w.int(v)
|
|
||||||
case uint:
|
|
||||||
return w.uint(uint64(v))
|
|
||||||
case uint8:
|
|
||||||
return w.uint(uint64(v))
|
|
||||||
case uint16:
|
|
||||||
return w.uint(uint64(v))
|
|
||||||
case uint32:
|
|
||||||
return w.uint(uint64(v))
|
|
||||||
case uint64:
|
|
||||||
return w.uint(v)
|
|
||||||
case float32:
|
|
||||||
return w.float(float64(v))
|
|
||||||
case float64:
|
|
||||||
return w.float(v)
|
|
||||||
case bool:
|
|
||||||
if v {
|
|
||||||
return w.int(1)
|
|
||||||
} else {
|
|
||||||
return w.int(0)
|
|
||||||
}
|
|
||||||
case encoding.BinaryMarshaler:
|
|
||||||
b, err := v.MarshalBinary()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.bytes(b)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf(
|
|
||||||
"redis: can't marshal %T (implement encoding.BinaryMarshaler)", v)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) bytes(b []byte) error {
|
|
||||||
err := w.wr.WriteByte(StringReply)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = w.writeLen(len(b))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = w.wr.Write(b)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return w.crlf()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) string(s string) error {
|
|
||||||
return w.bytes(util.StringToBytes(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) uint(n uint64) error {
|
|
||||||
w.numBuf = strconv.AppendUint(w.numBuf[:0], n, 10)
|
|
||||||
return w.bytes(w.numBuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) int(n int64) error {
|
|
||||||
w.numBuf = strconv.AppendInt(w.numBuf[:0], n, 10)
|
|
||||||
return w.bytes(w.numBuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) float(f float64) error {
|
|
||||||
w.numBuf = strconv.AppendFloat(w.numBuf[:0], f, 'f', -1, 64)
|
|
||||||
return w.bytes(w.numBuf)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) crlf() error {
|
|
||||||
err := w.wr.WriteByte('\r')
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return w.wr.WriteByte('\n')
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) Reset(wr io.Writer) {
|
|
||||||
w.wr.Reset(wr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (w *Writer) Flush() error {
|
|
||||||
return w.wr.Flush()
|
|
||||||
}
|
|
||||||
39
vendor/github.com/go-redis/redis/internal/util.go
сгенерированный
поставляемый
39
vendor/github.com/go-redis/redis/internal/util.go
сгенерированный
поставляемый
@@ -1,39 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import "github.com/go-redis/redis/internal/util"
|
|
||||||
|
|
||||||
func ToLower(s string) string {
|
|
||||||
if isLower(s) {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
b := make([]byte, len(s))
|
|
||||||
for i := range b {
|
|
||||||
c := s[i]
|
|
||||||
if c >= 'A' && c <= 'Z' {
|
|
||||||
c += 'a' - 'A'
|
|
||||||
}
|
|
||||||
b[i] = c
|
|
||||||
}
|
|
||||||
return util.BytesToString(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isLower(s string) bool {
|
|
||||||
for i := 0; i < len(s); i++ {
|
|
||||||
c := s[i]
|
|
||||||
if c >= 'A' && c <= 'Z' {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
func Unwrap(err error) error {
|
|
||||||
u, ok := err.(interface {
|
|
||||||
Unwrap() error
|
|
||||||
})
|
|
||||||
if !ok {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return u.Unwrap()
|
|
||||||
}
|
|
||||||
11
vendor/github.com/go-redis/redis/internal/util/safe.go
сгенерированный
поставляемый
11
vendor/github.com/go-redis/redis/internal/util/safe.go
сгенерированный
поставляемый
@@ -1,11 +0,0 @@
|
|||||||
// +build appengine
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
func BytesToString(b []byte) string {
|
|
||||||
return string(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringToBytes(s string) []byte {
|
|
||||||
return []byte(s)
|
|
||||||
}
|
|
||||||
19
vendor/github.com/go-redis/redis/internal/util/strconv.go
сгенерированный
поставляемый
19
vendor/github.com/go-redis/redis/internal/util/strconv.go
сгенерированный
поставляемый
@@ -1,19 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
import "strconv"
|
|
||||||
|
|
||||||
func Atoi(b []byte) (int, error) {
|
|
||||||
return strconv.Atoi(BytesToString(b))
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseInt(b []byte, base int, bitSize int) (int64, error) {
|
|
||||||
return strconv.ParseInt(BytesToString(b), base, bitSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseUint(b []byte, base int, bitSize int) (uint64, error) {
|
|
||||||
return strconv.ParseUint(BytesToString(b), base, bitSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseFloat(b []byte, bitSize int) (float64, error) {
|
|
||||||
return strconv.ParseFloat(BytesToString(b), bitSize)
|
|
||||||
}
|
|
||||||
22
vendor/github.com/go-redis/redis/internal/util/unsafe.go
сгенерированный
поставляемый
22
vendor/github.com/go-redis/redis/internal/util/unsafe.go
сгенерированный
поставляемый
@@ -1,22 +0,0 @@
|
|||||||
// +build !appengine
|
|
||||||
|
|
||||||
package util
|
|
||||||
|
|
||||||
import (
|
|
||||||
"unsafe"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BytesToString converts byte slice to string.
|
|
||||||
func BytesToString(b []byte) string {
|
|
||||||
return *(*string)(unsafe.Pointer(&b))
|
|
||||||
}
|
|
||||||
|
|
||||||
// StringToBytes converts string to byte slice.
|
|
||||||
func StringToBytes(s string) []byte {
|
|
||||||
return *(*[]byte)(unsafe.Pointer(
|
|
||||||
&struct {
|
|
||||||
string
|
|
||||||
Cap int
|
|
||||||
}{s, len(s)},
|
|
||||||
))
|
|
||||||
}
|
|
||||||
73
vendor/github.com/go-redis/redis/iterator.go
сгенерированный
поставляемый
73
vendor/github.com/go-redis/redis/iterator.go
сгенерированный
поставляемый
@@ -1,73 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import "sync"
|
|
||||||
|
|
||||||
// ScanIterator is used to incrementally iterate over a collection of elements.
|
|
||||||
// It's safe for concurrent use by multiple goroutines.
|
|
||||||
type ScanIterator struct {
|
|
||||||
mu sync.Mutex // protects Scanner and pos
|
|
||||||
cmd *ScanCmd
|
|
||||||
pos int
|
|
||||||
}
|
|
||||||
|
|
||||||
// Err returns the last iterator error, if any.
|
|
||||||
func (it *ScanIterator) Err() error {
|
|
||||||
it.mu.Lock()
|
|
||||||
err := it.cmd.Err()
|
|
||||||
it.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next advances the cursor and returns true if more values can be read.
|
|
||||||
func (it *ScanIterator) Next() bool {
|
|
||||||
it.mu.Lock()
|
|
||||||
defer it.mu.Unlock()
|
|
||||||
|
|
||||||
// Instantly return on errors.
|
|
||||||
if it.cmd.Err() != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Advance cursor, check if we are still within range.
|
|
||||||
if it.pos < len(it.cmd.page) {
|
|
||||||
it.pos++
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
for {
|
|
||||||
// Return if there is no more data to fetch.
|
|
||||||
if it.cmd.cursor == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fetch next page.
|
|
||||||
if it.cmd._args[0] == "scan" {
|
|
||||||
it.cmd._args[1] = it.cmd.cursor
|
|
||||||
} else {
|
|
||||||
it.cmd._args[2] = it.cmd.cursor
|
|
||||||
}
|
|
||||||
|
|
||||||
err := it.cmd.process(it.cmd)
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
it.pos = 1
|
|
||||||
|
|
||||||
// Redis can occasionally return empty page.
|
|
||||||
if len(it.cmd.page) > 0 {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Val returns the key/field at the current cursor position.
|
|
||||||
func (it *ScanIterator) Val() string {
|
|
||||||
var v string
|
|
||||||
it.mu.Lock()
|
|
||||||
if it.cmd.Err() == nil && it.pos > 0 && it.pos <= len(it.cmd.page) {
|
|
||||||
v = it.cmd.page[it.pos-1]
|
|
||||||
}
|
|
||||||
it.mu.Unlock()
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
226
vendor/github.com/go-redis/redis/options.go
сгенерированный
поставляемый
226
vendor/github.com/go-redis/redis/options.go
сгенерированный
поставляемый
@@ -1,226 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"net"
|
|
||||||
"net/url"
|
|
||||||
"runtime"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Limiter is the interface of a rate limiter or a circuit breaker.
|
|
||||||
type Limiter interface {
|
|
||||||
// Allow returns a nil if operation is allowed or an error otherwise.
|
|
||||||
// If operation is allowed client must report the result of operation
|
|
||||||
// whether is a success or a failure.
|
|
||||||
Allow() error
|
|
||||||
// ReportResult reports the result of previously allowed operation.
|
|
||||||
// nil indicates a success, non-nil error indicates a failure.
|
|
||||||
ReportResult(result error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Options struct {
|
|
||||||
// The network type, either tcp or unix.
|
|
||||||
// Default is tcp.
|
|
||||||
Network string
|
|
||||||
// host:port address.
|
|
||||||
Addr string
|
|
||||||
|
|
||||||
// Dialer creates new network connection and has priority over
|
|
||||||
// Network and Addr options.
|
|
||||||
Dialer func() (net.Conn, error)
|
|
||||||
|
|
||||||
// Hook that is called when new connection is established.
|
|
||||||
OnConnect func(*Conn) error
|
|
||||||
|
|
||||||
// Optional password. Must match the password specified in the
|
|
||||||
// requirepass server configuration option.
|
|
||||||
Password string
|
|
||||||
// Database to be selected after connecting to the server.
|
|
||||||
DB int
|
|
||||||
|
|
||||||
// Maximum number of retries before giving up.
|
|
||||||
// Default is to not retry failed commands.
|
|
||||||
MaxRetries int
|
|
||||||
// Minimum backoff between each retry.
|
|
||||||
// Default is 8 milliseconds; -1 disables backoff.
|
|
||||||
MinRetryBackoff time.Duration
|
|
||||||
// Maximum backoff between each retry.
|
|
||||||
// Default is 512 milliseconds; -1 disables backoff.
|
|
||||||
MaxRetryBackoff time.Duration
|
|
||||||
|
|
||||||
// Dial timeout for establishing new connections.
|
|
||||||
// Default is 5 seconds.
|
|
||||||
DialTimeout time.Duration
|
|
||||||
// Timeout for socket reads. If reached, commands will fail
|
|
||||||
// with a timeout instead of blocking. Use value -1 for no timeout and 0 for default.
|
|
||||||
// Default is 3 seconds.
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
// Timeout for socket writes. If reached, commands will fail
|
|
||||||
// with a timeout instead of blocking.
|
|
||||||
// Default is ReadTimeout.
|
|
||||||
WriteTimeout time.Duration
|
|
||||||
|
|
||||||
// Maximum number of socket connections.
|
|
||||||
// Default is 10 connections per every CPU as reported by runtime.NumCPU.
|
|
||||||
PoolSize int
|
|
||||||
// Minimum number of idle connections which is useful when establishing
|
|
||||||
// new connection is slow.
|
|
||||||
MinIdleConns int
|
|
||||||
// Connection age at which client retires (closes) the connection.
|
|
||||||
// Default is to not close aged connections.
|
|
||||||
MaxConnAge time.Duration
|
|
||||||
// Amount of time client waits for connection if all connections
|
|
||||||
// are busy before returning an error.
|
|
||||||
// Default is ReadTimeout + 1 second.
|
|
||||||
PoolTimeout time.Duration
|
|
||||||
// Amount of time after which client closes idle connections.
|
|
||||||
// Should be less than server's timeout.
|
|
||||||
// Default is 5 minutes. -1 disables idle timeout check.
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
// Frequency of idle checks made by idle connections reaper.
|
|
||||||
// Default is 1 minute. -1 disables idle connections reaper,
|
|
||||||
// but idle connections are still discarded by the client
|
|
||||||
// if IdleTimeout is set.
|
|
||||||
IdleCheckFrequency time.Duration
|
|
||||||
|
|
||||||
// Enables read only queries on slave nodes.
|
|
||||||
readOnly bool
|
|
||||||
|
|
||||||
// TLS Config to use. When set TLS will be negotiated.
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opt *Options) init() {
|
|
||||||
if opt.Network == "" {
|
|
||||||
opt.Network = "tcp"
|
|
||||||
}
|
|
||||||
if opt.Addr == "" {
|
|
||||||
opt.Addr = "localhost:6379"
|
|
||||||
}
|
|
||||||
if opt.Dialer == nil {
|
|
||||||
opt.Dialer = func() (net.Conn, error) {
|
|
||||||
netDialer := &net.Dialer{
|
|
||||||
Timeout: opt.DialTimeout,
|
|
||||||
KeepAlive: 5 * time.Minute,
|
|
||||||
}
|
|
||||||
if opt.TLSConfig == nil {
|
|
||||||
return netDialer.Dial(opt.Network, opt.Addr)
|
|
||||||
} else {
|
|
||||||
return tls.DialWithDialer(netDialer, opt.Network, opt.Addr, opt.TLSConfig)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if opt.PoolSize == 0 {
|
|
||||||
opt.PoolSize = 10 * runtime.NumCPU()
|
|
||||||
}
|
|
||||||
if opt.DialTimeout == 0 {
|
|
||||||
opt.DialTimeout = 5 * time.Second
|
|
||||||
}
|
|
||||||
switch opt.ReadTimeout {
|
|
||||||
case -1:
|
|
||||||
opt.ReadTimeout = 0
|
|
||||||
case 0:
|
|
||||||
opt.ReadTimeout = 3 * time.Second
|
|
||||||
}
|
|
||||||
switch opt.WriteTimeout {
|
|
||||||
case -1:
|
|
||||||
opt.WriteTimeout = 0
|
|
||||||
case 0:
|
|
||||||
opt.WriteTimeout = opt.ReadTimeout
|
|
||||||
}
|
|
||||||
if opt.PoolTimeout == 0 {
|
|
||||||
opt.PoolTimeout = opt.ReadTimeout + time.Second
|
|
||||||
}
|
|
||||||
if opt.IdleTimeout == 0 {
|
|
||||||
opt.IdleTimeout = 5 * time.Minute
|
|
||||||
}
|
|
||||||
if opt.IdleCheckFrequency == 0 {
|
|
||||||
opt.IdleCheckFrequency = time.Minute
|
|
||||||
}
|
|
||||||
|
|
||||||
switch opt.MinRetryBackoff {
|
|
||||||
case -1:
|
|
||||||
opt.MinRetryBackoff = 0
|
|
||||||
case 0:
|
|
||||||
opt.MinRetryBackoff = 8 * time.Millisecond
|
|
||||||
}
|
|
||||||
switch opt.MaxRetryBackoff {
|
|
||||||
case -1:
|
|
||||||
opt.MaxRetryBackoff = 0
|
|
||||||
case 0:
|
|
||||||
opt.MaxRetryBackoff = 512 * time.Millisecond
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ParseURL parses an URL into Options that can be used to connect to Redis.
|
|
||||||
func ParseURL(redisURL string) (*Options, error) {
|
|
||||||
o := &Options{Network: "tcp"}
|
|
||||||
u, err := url.Parse(redisURL)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme != "redis" && u.Scheme != "rediss" {
|
|
||||||
return nil, errors.New("invalid redis URL scheme: " + u.Scheme)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.User != nil {
|
|
||||||
if p, ok := u.User.Password(); ok {
|
|
||||||
o.Password = p
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(u.Query()) > 0 {
|
|
||||||
return nil, errors.New("no options supported")
|
|
||||||
}
|
|
||||||
|
|
||||||
h, p, err := net.SplitHostPort(u.Host)
|
|
||||||
if err != nil {
|
|
||||||
h = u.Host
|
|
||||||
}
|
|
||||||
if h == "" {
|
|
||||||
h = "localhost"
|
|
||||||
}
|
|
||||||
if p == "" {
|
|
||||||
p = "6379"
|
|
||||||
}
|
|
||||||
o.Addr = net.JoinHostPort(h, p)
|
|
||||||
|
|
||||||
f := strings.FieldsFunc(u.Path, func(r rune) bool {
|
|
||||||
return r == '/'
|
|
||||||
})
|
|
||||||
switch len(f) {
|
|
||||||
case 0:
|
|
||||||
o.DB = 0
|
|
||||||
case 1:
|
|
||||||
if o.DB, err = strconv.Atoi(f[0]); err != nil {
|
|
||||||
return nil, fmt.Errorf("invalid redis database number: %q", f[0])
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, errors.New("invalid redis URL path: " + u.Path)
|
|
||||||
}
|
|
||||||
|
|
||||||
if u.Scheme == "rediss" {
|
|
||||||
o.TLSConfig = &tls.Config{ServerName: h}
|
|
||||||
}
|
|
||||||
return o, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConnPool(opt *Options) *pool.ConnPool {
|
|
||||||
return pool.NewConnPool(&pool.Options{
|
|
||||||
Dialer: opt.Dialer,
|
|
||||||
PoolSize: opt.PoolSize,
|
|
||||||
MinIdleConns: opt.MinIdleConns,
|
|
||||||
MaxConnAge: opt.MaxConnAge,
|
|
||||||
PoolTimeout: opt.PoolTimeout,
|
|
||||||
IdleTimeout: opt.IdleTimeout,
|
|
||||||
IdleCheckFrequency: opt.IdleCheckFrequency,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
133
vendor/github.com/go-redis/redis/pipeline.go
сгенерированный
поставляемый
133
vendor/github.com/go-redis/redis/pipeline.go
сгенерированный
поставляемый
@@ -1,133 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
type pipelineExecer func([]Cmder) error
|
|
||||||
|
|
||||||
// Pipeliner is an mechanism to realise Redis Pipeline technique.
|
|
||||||
//
|
|
||||||
// Pipelining is a technique to extremely speed up processing by packing
|
|
||||||
// operations to batches, send them at once to Redis and read a replies in a
|
|
||||||
// singe step.
|
|
||||||
// See https://redis.io/topics/pipelining
|
|
||||||
//
|
|
||||||
// Pay attention, that Pipeline is not a transaction, so you can get unexpected
|
|
||||||
// results in case of big pipelines and small read/write timeouts.
|
|
||||||
// Redis client has retransmission logic in case of timeouts, pipeline
|
|
||||||
// can be retransmitted and commands can be executed more then once.
|
|
||||||
// To avoid this: it is good idea to use reasonable bigger read/write timeouts
|
|
||||||
// depends of your batch size and/or use TxPipeline.
|
|
||||||
type Pipeliner interface {
|
|
||||||
StatefulCmdable
|
|
||||||
Do(args ...interface{}) *Cmd
|
|
||||||
Process(cmd Cmder) error
|
|
||||||
Close() error
|
|
||||||
Discard() error
|
|
||||||
Exec() ([]Cmder, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ Pipeliner = (*Pipeline)(nil)
|
|
||||||
|
|
||||||
// Pipeline implements pipelining as described in
|
|
||||||
// http://redis.io/topics/pipelining. It's safe for concurrent use
|
|
||||||
// by multiple goroutines.
|
|
||||||
type Pipeline struct {
|
|
||||||
statefulCmdable
|
|
||||||
|
|
||||||
exec pipelineExecer
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
cmds []Cmder
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) Do(args ...interface{}) *Cmd {
|
|
||||||
cmd := NewCmd(args...)
|
|
||||||
_ = c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process queues the cmd for later execution.
|
|
||||||
func (c *Pipeline) Process(cmd Cmder) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.cmds = append(c.cmds, cmd)
|
|
||||||
c.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the pipeline, releasing any open resources.
|
|
||||||
func (c *Pipeline) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
c.discard()
|
|
||||||
c.closed = true
|
|
||||||
c.mu.Unlock()
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Discard resets the pipeline and discards queued commands.
|
|
||||||
func (c *Pipeline) Discard() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
err := c.discard()
|
|
||||||
c.mu.Unlock()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) discard() error {
|
|
||||||
if c.closed {
|
|
||||||
return pool.ErrClosed
|
|
||||||
}
|
|
||||||
c.cmds = c.cmds[:0]
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Exec executes all previously queued commands using one
|
|
||||||
// client-server roundtrip.
|
|
||||||
//
|
|
||||||
// Exec always returns list of commands and error of the first failed
|
|
||||||
// command if any.
|
|
||||||
func (c *Pipeline) Exec() ([]Cmder, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.closed {
|
|
||||||
return nil, pool.ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.cmds) == 0 {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds := c.cmds
|
|
||||||
c.cmds = nil
|
|
||||||
|
|
||||||
return cmds, c.exec(cmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
if err := fn(c); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
cmds, err := c.Exec()
|
|
||||||
_ = c.Close()
|
|
||||||
return cmds, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) Pipeline() Pipeliner {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Pipeline) TxPipeline() Pipeliner {
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
513
vendor/github.com/go-redis/redis/pubsub.go
сгенерированный
поставляемый
513
vendor/github.com/go-redis/redis/pubsub.go
сгенерированный
поставляемый
@@ -1,513 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal"
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
"github.com/go-redis/redis/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
var errPingTimeout = errors.New("redis: ping timeout")
|
|
||||||
|
|
||||||
// PubSub implements Pub/Sub commands as described in
|
|
||||||
// http://redis.io/topics/pubsub. Message receiving is NOT safe
|
|
||||||
// for concurrent use by multiple goroutines.
|
|
||||||
//
|
|
||||||
// PubSub automatically reconnects to Redis Server and resubscribes
|
|
||||||
// to the channels in case of network errors.
|
|
||||||
type PubSub struct {
|
|
||||||
opt *Options
|
|
||||||
|
|
||||||
newConn func([]string) (*pool.Conn, error)
|
|
||||||
closeConn func(*pool.Conn) error
|
|
||||||
|
|
||||||
mu sync.Mutex
|
|
||||||
cn *pool.Conn
|
|
||||||
channels map[string]struct{}
|
|
||||||
patterns map[string]struct{}
|
|
||||||
|
|
||||||
closed bool
|
|
||||||
exit chan struct{}
|
|
||||||
|
|
||||||
cmd *Cmd
|
|
||||||
|
|
||||||
chOnce sync.Once
|
|
||||||
ch chan *Message
|
|
||||||
ping chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) String() string {
|
|
||||||
channels := mapKeys(c.channels)
|
|
||||||
channels = append(channels, mapKeys(c.patterns)...)
|
|
||||||
return fmt.Sprintf("PubSub(%s)", strings.Join(channels, ", "))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) init() {
|
|
||||||
c.exit = make(chan struct{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) conn() (*pool.Conn, error) {
|
|
||||||
c.mu.Lock()
|
|
||||||
cn, err := c._conn(nil)
|
|
||||||
c.mu.Unlock()
|
|
||||||
return cn, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) _conn(newChannels []string) (*pool.Conn, error) {
|
|
||||||
if c.closed {
|
|
||||||
return nil, pool.ErrClosed
|
|
||||||
}
|
|
||||||
if c.cn != nil {
|
|
||||||
return c.cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
channels := mapKeys(c.channels)
|
|
||||||
channels = append(channels, newChannels...)
|
|
||||||
|
|
||||||
cn, err := c.newConn(channels)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := c.resubscribe(cn); err != nil {
|
|
||||||
_ = c.closeConn(cn)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
c.cn = cn
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) writeCmd(cn *pool.Conn, cmd Cmder) error {
|
|
||||||
return cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return writeCmd(wr, cmd)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) resubscribe(cn *pool.Conn) error {
|
|
||||||
var firstErr error
|
|
||||||
|
|
||||||
if len(c.channels) > 0 {
|
|
||||||
err := c._subscribe(cn, "subscribe", mapKeys(c.channels))
|
|
||||||
if err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(c.patterns) > 0 {
|
|
||||||
err := c._subscribe(cn, "psubscribe", mapKeys(c.patterns))
|
|
||||||
if err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func mapKeys(m map[string]struct{}) []string {
|
|
||||||
s := make([]string, len(m))
|
|
||||||
i := 0
|
|
||||||
for k := range m {
|
|
||||||
s[i] = k
|
|
||||||
i++
|
|
||||||
}
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) _subscribe(
|
|
||||||
cn *pool.Conn, redisCmd string, channels []string,
|
|
||||||
) error {
|
|
||||||
args := make([]interface{}, 0, 1+len(channels))
|
|
||||||
args = append(args, redisCmd)
|
|
||||||
for _, channel := range channels {
|
|
||||||
args = append(args, channel)
|
|
||||||
}
|
|
||||||
cmd := NewSliceCmd(args...)
|
|
||||||
return c.writeCmd(cn, cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) releaseConn(cn *pool.Conn, err error, allowTimeout bool) {
|
|
||||||
c.mu.Lock()
|
|
||||||
c._releaseConn(cn, err, allowTimeout)
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) _releaseConn(cn *pool.Conn, err error, allowTimeout bool) {
|
|
||||||
if c.cn != cn {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if internal.IsBadConn(err, allowTimeout) {
|
|
||||||
c._reconnect(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) _reconnect(reason error) {
|
|
||||||
_ = c._closeTheCn(reason)
|
|
||||||
_, _ = c._conn(nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) _closeTheCn(reason error) error {
|
|
||||||
if c.cn == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !c.closed {
|
|
||||||
internal.Logf("redis: discarding bad PubSub connection: %s", reason)
|
|
||||||
}
|
|
||||||
err := c.closeConn(c.cn)
|
|
||||||
c.cn = nil
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.closed {
|
|
||||||
return pool.ErrClosed
|
|
||||||
}
|
|
||||||
c.closed = true
|
|
||||||
close(c.exit)
|
|
||||||
|
|
||||||
err := c._closeTheCn(pool.ErrClosed)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe the client to the specified channels. It returns
|
|
||||||
// empty subscription if there are no channels.
|
|
||||||
func (c *PubSub) Subscribe(channels ...string) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
err := c.subscribe("subscribe", channels...)
|
|
||||||
if c.channels == nil {
|
|
||||||
c.channels = make(map[string]struct{})
|
|
||||||
}
|
|
||||||
for _, s := range channels {
|
|
||||||
c.channels[s] = struct{}{}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSubscribe the client to the given patterns. It returns
|
|
||||||
// empty subscription if there are no patterns.
|
|
||||||
func (c *PubSub) PSubscribe(patterns ...string) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
err := c.subscribe("psubscribe", patterns...)
|
|
||||||
if c.patterns == nil {
|
|
||||||
c.patterns = make(map[string]struct{})
|
|
||||||
}
|
|
||||||
for _, s := range patterns {
|
|
||||||
c.patterns[s] = struct{}{}
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unsubscribe the client from the given channels, or from all of
|
|
||||||
// them if none is given.
|
|
||||||
func (c *PubSub) Unsubscribe(channels ...string) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
for _, channel := range channels {
|
|
||||||
delete(c.channels, channel)
|
|
||||||
}
|
|
||||||
err := c.subscribe("unsubscribe", channels...)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// PUnsubscribe the client from the given patterns, or from all of
|
|
||||||
// them if none is given.
|
|
||||||
func (c *PubSub) PUnsubscribe(patterns ...string) error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
for _, pattern := range patterns {
|
|
||||||
delete(c.patterns, pattern)
|
|
||||||
}
|
|
||||||
err := c.subscribe("punsubscribe", patterns...)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) subscribe(redisCmd string, channels ...string) error {
|
|
||||||
cn, err := c._conn(channels)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c._subscribe(cn, redisCmd, channels)
|
|
||||||
c._releaseConn(cn, err, false)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) Ping(payload ...string) error {
|
|
||||||
args := []interface{}{"ping"}
|
|
||||||
if len(payload) == 1 {
|
|
||||||
args = append(args, payload[0])
|
|
||||||
}
|
|
||||||
cmd := NewCmd(args...)
|
|
||||||
|
|
||||||
cn, err := c.conn()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.writeCmd(cn, cmd)
|
|
||||||
c.releaseConn(cn, err, false)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscription received after a successful subscription to channel.
|
|
||||||
type Subscription struct {
|
|
||||||
// Can be "subscribe", "unsubscribe", "psubscribe" or "punsubscribe".
|
|
||||||
Kind string
|
|
||||||
// Channel name we have subscribed to.
|
|
||||||
Channel string
|
|
||||||
// Number of channels we are currently subscribed to.
|
|
||||||
Count int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Subscription) String() string {
|
|
||||||
return fmt.Sprintf("%s: %s", m.Kind, m.Channel)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Message received as result of a PUBLISH command issued by another client.
|
|
||||||
type Message struct {
|
|
||||||
Channel string
|
|
||||||
Pattern string
|
|
||||||
Payload string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *Message) String() string {
|
|
||||||
return fmt.Sprintf("Message<%s: %s>", m.Channel, m.Payload)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pong received as result of a PING command issued by another client.
|
|
||||||
type Pong struct {
|
|
||||||
Payload string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *Pong) String() string {
|
|
||||||
if p.Payload != "" {
|
|
||||||
return fmt.Sprintf("Pong<%s>", p.Payload)
|
|
||||||
}
|
|
||||||
return "Pong"
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) newMessage(reply interface{}) (interface{}, error) {
|
|
||||||
switch reply := reply.(type) {
|
|
||||||
case string:
|
|
||||||
return &Pong{
|
|
||||||
Payload: reply,
|
|
||||||
}, nil
|
|
||||||
case []interface{}:
|
|
||||||
switch kind := reply[0].(string); kind {
|
|
||||||
case "subscribe", "unsubscribe", "psubscribe", "punsubscribe":
|
|
||||||
return &Subscription{
|
|
||||||
Kind: kind,
|
|
||||||
Channel: reply[1].(string),
|
|
||||||
Count: int(reply[2].(int64)),
|
|
||||||
}, nil
|
|
||||||
case "message":
|
|
||||||
return &Message{
|
|
||||||
Channel: reply[1].(string),
|
|
||||||
Payload: reply[2].(string),
|
|
||||||
}, nil
|
|
||||||
case "pmessage":
|
|
||||||
return &Message{
|
|
||||||
Pattern: reply[1].(string),
|
|
||||||
Channel: reply[2].(string),
|
|
||||||
Payload: reply[3].(string),
|
|
||||||
}, nil
|
|
||||||
case "pong":
|
|
||||||
return &Pong{
|
|
||||||
Payload: reply[1].(string),
|
|
||||||
}, nil
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("redis: unsupported pubsub message: %q", kind)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return nil, fmt.Errorf("redis: unsupported pubsub message: %#v", reply)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveTimeout acts like Receive but returns an error if message
|
|
||||||
// is not received in time. This is low-level API and in most cases
|
|
||||||
// Channel should be used instead.
|
|
||||||
func (c *PubSub) ReceiveTimeout(timeout time.Duration) (interface{}, error) {
|
|
||||||
if c.cmd == nil {
|
|
||||||
c.cmd = NewCmd()
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := c.conn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithReader(timeout, func(rd *proto.Reader) error {
|
|
||||||
return c.cmd.readReply(rd)
|
|
||||||
})
|
|
||||||
|
|
||||||
c.releaseConn(cn, err, timeout > 0)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.newMessage(c.cmd.Val())
|
|
||||||
}
|
|
||||||
|
|
||||||
// Receive returns a message as a Subscription, Message, Pong or error.
|
|
||||||
// See PubSub example for details. This is low-level API and in most cases
|
|
||||||
// Channel should be used instead.
|
|
||||||
func (c *PubSub) Receive() (interface{}, error) {
|
|
||||||
return c.ReceiveTimeout(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ReceiveMessage returns a Message or error ignoring Subscription and Pong
|
|
||||||
// messages. This is low-level API and in most cases Channel should be used
|
|
||||||
// instead.
|
|
||||||
func (c *PubSub) ReceiveMessage() (*Message, error) {
|
|
||||||
for {
|
|
||||||
msg, err := c.Receive()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *Subscription:
|
|
||||||
// Ignore.
|
|
||||||
case *Pong:
|
|
||||||
// Ignore.
|
|
||||||
case *Message:
|
|
||||||
return msg, nil
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("redis: unknown message: %T", msg)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Channel returns a Go channel for concurrently receiving messages.
|
|
||||||
// It periodically sends Ping messages to test connection health.
|
|
||||||
// The channel is closed with PubSub. Receive* APIs can not be used
|
|
||||||
// after channel is created.
|
|
||||||
//
|
|
||||||
// If the Go channel is full for 30 seconds the message is dropped.
|
|
||||||
func (c *PubSub) Channel() <-chan *Message {
|
|
||||||
return c.channel(100)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChannelSize is like Channel, but creates a Go channel
|
|
||||||
// with specified buffer size.
|
|
||||||
func (c *PubSub) ChannelSize(size int) <-chan *Message {
|
|
||||||
return c.channel(size)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) channel(size int) <-chan *Message {
|
|
||||||
c.chOnce.Do(func() {
|
|
||||||
c.initChannel(size)
|
|
||||||
})
|
|
||||||
if cap(c.ch) != size {
|
|
||||||
err := fmt.Errorf("redis: PubSub.Channel is called with different buffer size")
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return c.ch
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) initChannel(size int) {
|
|
||||||
const timeout = 30 * time.Second
|
|
||||||
|
|
||||||
c.ch = make(chan *Message, size)
|
|
||||||
c.ping = make(chan struct{}, 1)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
timer := time.NewTimer(timeout)
|
|
||||||
timer.Stop()
|
|
||||||
|
|
||||||
var errCount int
|
|
||||||
for {
|
|
||||||
msg, err := c.Receive()
|
|
||||||
if err != nil {
|
|
||||||
if err == pool.ErrClosed {
|
|
||||||
close(c.ch)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if errCount > 0 {
|
|
||||||
time.Sleep(c.retryBackoff(errCount))
|
|
||||||
}
|
|
||||||
errCount++
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
errCount = 0
|
|
||||||
|
|
||||||
// Any message is as good as a ping.
|
|
||||||
select {
|
|
||||||
case c.ping <- struct{}{}:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case *Subscription:
|
|
||||||
// Ignore.
|
|
||||||
case *Pong:
|
|
||||||
// Ignore.
|
|
||||||
case *Message:
|
|
||||||
timer.Reset(timeout)
|
|
||||||
select {
|
|
||||||
case c.ch <- msg:
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
case <-timer.C:
|
|
||||||
internal.Logf(
|
|
||||||
"redis: %s channel is full for %s (message is dropped)",
|
|
||||||
c, timeout)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
internal.Logf("redis: unknown message type: %T", msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
timer := time.NewTimer(timeout)
|
|
||||||
timer.Stop()
|
|
||||||
|
|
||||||
healthy := true
|
|
||||||
for {
|
|
||||||
timer.Reset(timeout)
|
|
||||||
select {
|
|
||||||
case <-c.ping:
|
|
||||||
healthy = true
|
|
||||||
if !timer.Stop() {
|
|
||||||
<-timer.C
|
|
||||||
}
|
|
||||||
case <-timer.C:
|
|
||||||
pingErr := c.Ping()
|
|
||||||
if healthy {
|
|
||||||
healthy = false
|
|
||||||
} else {
|
|
||||||
if pingErr == nil {
|
|
||||||
pingErr = errPingTimeout
|
|
||||||
}
|
|
||||||
c.mu.Lock()
|
|
||||||
c._reconnect(pingErr)
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
case <-c.exit:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *PubSub) retryBackoff(attempt int) time.Duration {
|
|
||||||
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
|
||||||
}
|
|
||||||
583
vendor/github.com/go-redis/redis/redis.go
сгенерированный
поставляемый
583
vendor/github.com/go-redis/redis/redis.go
сгенерированный
поставляемый
@@ -1,583 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal"
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
"github.com/go-redis/redis/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Nil reply Redis returns when key does not exist.
|
|
||||||
const Nil = proto.Nil
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
SetLogger(log.New(os.Stderr, "redis: ", log.LstdFlags|log.Lshortfile))
|
|
||||||
}
|
|
||||||
|
|
||||||
func SetLogger(logger *log.Logger) {
|
|
||||||
internal.Logger = logger
|
|
||||||
}
|
|
||||||
|
|
||||||
type baseClient struct {
|
|
||||||
opt *Options
|
|
||||||
connPool pool.Pooler
|
|
||||||
limiter Limiter
|
|
||||||
|
|
||||||
process func(Cmder) error
|
|
||||||
processPipeline func([]Cmder) error
|
|
||||||
processTxPipeline func([]Cmder) error
|
|
||||||
|
|
||||||
onClose func() error // hook called when client is closed
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) init() {
|
|
||||||
c.process = c.defaultProcess
|
|
||||||
c.processPipeline = c.defaultProcessPipeline
|
|
||||||
c.processTxPipeline = c.defaultProcessTxPipeline
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) String() string {
|
|
||||||
return fmt.Sprintf("Redis<%s db:%d>", c.getAddr(), c.opt.DB)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) newConn() (*pool.Conn, error) {
|
|
||||||
cn, err := c.connPool.NewConn()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.initConn(cn)
|
|
||||||
if err != nil {
|
|
||||||
_ = c.connPool.CloseConn(cn)
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) getConn() (*pool.Conn, error) {
|
|
||||||
if c.limiter != nil {
|
|
||||||
err := c.limiter.Allow()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := c._getConn()
|
|
||||||
if err != nil {
|
|
||||||
if c.limiter != nil {
|
|
||||||
c.limiter.ReportResult(err)
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) _getConn() (*pool.Conn, error) {
|
|
||||||
cn, err := c.connPool.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = c.initConn(cn)
|
|
||||||
if err != nil {
|
|
||||||
c.connPool.Remove(cn, err)
|
|
||||||
if err := internal.Unwrap(err); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cn, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) releaseConn(cn *pool.Conn, err error) {
|
|
||||||
if c.limiter != nil {
|
|
||||||
c.limiter.ReportResult(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if internal.IsBadConn(err, false) {
|
|
||||||
c.connPool.Remove(cn, err)
|
|
||||||
} else {
|
|
||||||
c.connPool.Put(cn)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) releaseConnStrict(cn *pool.Conn, err error) {
|
|
||||||
if c.limiter != nil {
|
|
||||||
c.limiter.ReportResult(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err == nil || internal.IsRedisError(err) {
|
|
||||||
c.connPool.Put(cn)
|
|
||||||
} else {
|
|
||||||
c.connPool.Remove(cn, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) initConn(cn *pool.Conn) error {
|
|
||||||
if cn.Inited {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cn.Inited = true
|
|
||||||
|
|
||||||
if c.opt.Password == "" &&
|
|
||||||
c.opt.DB == 0 &&
|
|
||||||
!c.opt.readOnly &&
|
|
||||||
c.opt.OnConnect == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
conn := newConn(c.opt, cn)
|
|
||||||
_, err := conn.Pipelined(func(pipe Pipeliner) error {
|
|
||||||
if c.opt.Password != "" {
|
|
||||||
pipe.Auth(c.opt.Password)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.opt.DB > 0 {
|
|
||||||
pipe.Select(c.opt.DB)
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.opt.readOnly {
|
|
||||||
pipe.ReadOnly()
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if c.opt.OnConnect != nil {
|
|
||||||
return c.opt.OnConnect(conn)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do creates a Cmd from the args and processes the cmd.
|
|
||||||
func (c *baseClient) Do(args ...interface{}) *Cmd {
|
|
||||||
cmd := NewCmd(args...)
|
|
||||||
_ = c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// WrapProcess wraps function that processes Redis commands.
|
|
||||||
func (c *baseClient) WrapProcess(
|
|
||||||
fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error,
|
|
||||||
) {
|
|
||||||
c.process = fn(c.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) Process(cmd Cmder) error {
|
|
||||||
return c.process(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) defaultProcess(cmd Cmder) error {
|
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
|
||||||
if attempt > 0 {
|
|
||||||
time.Sleep(c.retryBackoff(attempt))
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := c.getConn()
|
|
||||||
if err != nil {
|
|
||||||
cmd.setErr(err)
|
|
||||||
if internal.IsRetryableError(err, true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return writeCmd(wr, cmd)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
c.releaseConn(cn, err)
|
|
||||||
cmd.setErr(err)
|
|
||||||
if internal.IsRetryableError(err, true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithReader(c.cmdTimeout(cmd), cmd.readReply)
|
|
||||||
c.releaseConn(cn, err)
|
|
||||||
if err != nil && internal.IsRetryableError(err, cmd.readTimeout() == nil) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmd.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) retryBackoff(attempt int) time.Duration {
|
|
||||||
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) cmdTimeout(cmd Cmder) time.Duration {
|
|
||||||
if timeout := cmd.readTimeout(); timeout != nil {
|
|
||||||
t := *timeout
|
|
||||||
if t == 0 {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return t + 10*time.Second
|
|
||||||
}
|
|
||||||
return c.opt.ReadTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the client, releasing any open resources.
|
|
||||||
//
|
|
||||||
// It is rare to Close a Client, as the Client is meant to be
|
|
||||||
// long-lived and shared between many goroutines.
|
|
||||||
func (c *baseClient) Close() error {
|
|
||||||
var firstErr error
|
|
||||||
if c.onClose != nil {
|
|
||||||
if err := c.onClose(); err != nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := c.connPool.Close(); err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) getAddr() string {
|
|
||||||
return c.opt.Addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) WrapProcessPipeline(
|
|
||||||
fn func(oldProcess func([]Cmder) error) func([]Cmder) error,
|
|
||||||
) {
|
|
||||||
c.processPipeline = fn(c.processPipeline)
|
|
||||||
c.processTxPipeline = fn(c.processTxPipeline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) defaultProcessPipeline(cmds []Cmder) error {
|
|
||||||
return c.generalProcessPipeline(cmds, c.pipelineProcessCmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) defaultProcessTxPipeline(cmds []Cmder) error {
|
|
||||||
return c.generalProcessPipeline(cmds, c.txPipelineProcessCmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
type pipelineProcessor func(*pool.Conn, []Cmder) (bool, error)
|
|
||||||
|
|
||||||
func (c *baseClient) generalProcessPipeline(cmds []Cmder, p pipelineProcessor) error {
|
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
|
||||||
if attempt > 0 {
|
|
||||||
time.Sleep(c.retryBackoff(attempt))
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := c.getConn()
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
canRetry, err := p(cn, cmds)
|
|
||||||
c.releaseConnStrict(cn, err)
|
|
||||||
|
|
||||||
if !canRetry || !internal.IsRetryableError(err, true) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cmdsFirstErr(cmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) pipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) {
|
|
||||||
err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return writeCmd(wr, cmds...)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
|
||||||
return pipelineReadCmds(rd, cmds)
|
|
||||||
})
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func pipelineReadCmds(rd *proto.Reader, cmds []Cmder) error {
|
|
||||||
for _, cmd := range cmds {
|
|
||||||
err := cmd.readReply(rd)
|
|
||||||
if err != nil && !internal.IsRedisError(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *baseClient) txPipelineProcessCmds(cn *pool.Conn, cmds []Cmder) (bool, error) {
|
|
||||||
err := cn.WithWriter(c.opt.WriteTimeout, func(wr *proto.Writer) error {
|
|
||||||
return txPipelineWriteMulti(wr, cmds)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cn.WithReader(c.opt.ReadTimeout, func(rd *proto.Reader) error {
|
|
||||||
err := txPipelineReadQueued(rd, cmds)
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return pipelineReadCmds(rd, cmds)
|
|
||||||
})
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func txPipelineWriteMulti(wr *proto.Writer, cmds []Cmder) error {
|
|
||||||
multiExec := make([]Cmder, 0, len(cmds)+2)
|
|
||||||
multiExec = append(multiExec, NewStatusCmd("MULTI"))
|
|
||||||
multiExec = append(multiExec, cmds...)
|
|
||||||
multiExec = append(multiExec, NewSliceCmd("EXEC"))
|
|
||||||
return writeCmd(wr, multiExec...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func txPipelineReadQueued(rd *proto.Reader, cmds []Cmder) error {
|
|
||||||
// Parse queued replies.
|
|
||||||
var statusCmd StatusCmd
|
|
||||||
err := statusCmd.readReply(rd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for range cmds {
|
|
||||||
err = statusCmd.readReply(rd)
|
|
||||||
if err != nil && !internal.IsRedisError(err) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse number of replies.
|
|
||||||
line, err := rd.ReadLine()
|
|
||||||
if err != nil {
|
|
||||||
if err == Nil {
|
|
||||||
err = TxFailedErr
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
switch line[0] {
|
|
||||||
case proto.ErrorReply:
|
|
||||||
return proto.ParseErrorReply(line)
|
|
||||||
case proto.ArrayReply:
|
|
||||||
// ok
|
|
||||||
default:
|
|
||||||
err := fmt.Errorf("redis: expected '*', but got line %q", line)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Client is a Redis client representing a pool of zero or more
|
|
||||||
// underlying connections. It's safe for concurrent use by multiple
|
|
||||||
// goroutines.
|
|
||||||
type Client struct {
|
|
||||||
baseClient
|
|
||||||
cmdable
|
|
||||||
|
|
||||||
ctx context.Context
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClient returns a client to the Redis Server specified by Options.
|
|
||||||
func NewClient(opt *Options) *Client {
|
|
||||||
opt.init()
|
|
||||||
|
|
||||||
c := Client{
|
|
||||||
baseClient: baseClient{
|
|
||||||
opt: opt,
|
|
||||||
connPool: newConnPool(opt),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.baseClient.init()
|
|
||||||
c.init()
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) init() {
|
|
||||||
c.cmdable.setProcessor(c.Process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Context() context.Context {
|
|
||||||
if c.ctx != nil {
|
|
||||||
return c.ctx
|
|
||||||
}
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) WithContext(ctx context.Context) *Client {
|
|
||||||
if ctx == nil {
|
|
||||||
panic("nil context")
|
|
||||||
}
|
|
||||||
c2 := c.clone()
|
|
||||||
c2.ctx = ctx
|
|
||||||
return c2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) clone() *Client {
|
|
||||||
cp := *c
|
|
||||||
cp.init()
|
|
||||||
return &cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options returns read-only Options that were used to create the client.
|
|
||||||
func (c *Client) Options() *Options {
|
|
||||||
return c.opt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) SetLimiter(l Limiter) *Client {
|
|
||||||
c.limiter = l
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
type PoolStats pool.Stats
|
|
||||||
|
|
||||||
// PoolStats returns connection pool stats.
|
|
||||||
func (c *Client) PoolStats() *PoolStats {
|
|
||||||
stats := c.connPool.Stats()
|
|
||||||
return (*PoolStats)(stats)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.Pipeline().Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) Pipeline() Pipeliner {
|
|
||||||
pipe := Pipeline{
|
|
||||||
exec: c.processPipeline,
|
|
||||||
}
|
|
||||||
pipe.statefulCmdable.setProcessor(pipe.Process)
|
|
||||||
return &pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.TxPipeline().Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
|
||||||
func (c *Client) TxPipeline() Pipeliner {
|
|
||||||
pipe := Pipeline{
|
|
||||||
exec: c.processTxPipeline,
|
|
||||||
}
|
|
||||||
pipe.statefulCmdable.setProcessor(pipe.Process)
|
|
||||||
return &pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) pubSub() *PubSub {
|
|
||||||
pubsub := &PubSub{
|
|
||||||
opt: c.opt,
|
|
||||||
|
|
||||||
newConn: func(channels []string) (*pool.Conn, error) {
|
|
||||||
return c.newConn()
|
|
||||||
},
|
|
||||||
closeConn: c.connPool.CloseConn,
|
|
||||||
}
|
|
||||||
pubsub.init()
|
|
||||||
return pubsub
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe subscribes the client to the specified channels.
|
|
||||||
// Channels can be omitted to create empty subscription.
|
|
||||||
// Note that this method does not wait on a response from Redis, so the
|
|
||||||
// subscription may not be active immediately. To force the connection to wait,
|
|
||||||
// you may call the Receive() method on the returned *PubSub like so:
|
|
||||||
//
|
|
||||||
// sub := client.Subscribe(queryResp)
|
|
||||||
// iface, err := sub.Receive()
|
|
||||||
// if err != nil {
|
|
||||||
// // handle error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Should be *Subscription, but others are possible if other actions have been
|
|
||||||
// // taken on sub since it was created.
|
|
||||||
// switch iface.(type) {
|
|
||||||
// case *Subscription:
|
|
||||||
// // subscribe succeeded
|
|
||||||
// case *Message:
|
|
||||||
// // received first message
|
|
||||||
// case *Pong:
|
|
||||||
// // pong received
|
|
||||||
// default:
|
|
||||||
// // handle error
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ch := sub.Channel()
|
|
||||||
func (c *Client) Subscribe(channels ...string) *PubSub {
|
|
||||||
pubsub := c.pubSub()
|
|
||||||
if len(channels) > 0 {
|
|
||||||
_ = pubsub.Subscribe(channels...)
|
|
||||||
}
|
|
||||||
return pubsub
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSubscribe subscribes the client to the given patterns.
|
|
||||||
// Patterns can be omitted to create empty subscription.
|
|
||||||
func (c *Client) PSubscribe(channels ...string) *PubSub {
|
|
||||||
pubsub := c.pubSub()
|
|
||||||
if len(channels) > 0 {
|
|
||||||
_ = pubsub.PSubscribe(channels...)
|
|
||||||
}
|
|
||||||
return pubsub
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Conn is like Client, but its pool contains single connection.
|
|
||||||
type Conn struct {
|
|
||||||
baseClient
|
|
||||||
statefulCmdable
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConn(opt *Options, cn *pool.Conn) *Conn {
|
|
||||||
connPool := pool.NewSingleConnPool(nil)
|
|
||||||
connPool.SetConn(cn)
|
|
||||||
c := Conn{
|
|
||||||
baseClient: baseClient{
|
|
||||||
opt: opt,
|
|
||||||
connPool: connPool,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.baseClient.init()
|
|
||||||
c.statefulCmdable.setProcessor(c.Process)
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.Pipeline().Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) Pipeline() Pipeliner {
|
|
||||||
pipe := Pipeline{
|
|
||||||
exec: c.processPipeline,
|
|
||||||
}
|
|
||||||
pipe.statefulCmdable.setProcessor(pipe.Process)
|
|
||||||
return &pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Conn) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.TxPipeline().Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxPipeline acts like Pipeline, but wraps queued commands with MULTI/EXEC.
|
|
||||||
func (c *Conn) TxPipeline() Pipeliner {
|
|
||||||
pipe := Pipeline{
|
|
||||||
exec: c.processTxPipeline,
|
|
||||||
}
|
|
||||||
pipe.statefulCmdable.setProcessor(pipe.Process)
|
|
||||||
return &pipe
|
|
||||||
}
|
|
||||||
140
vendor/github.com/go-redis/redis/result.go
сгенерированный
поставляемый
140
vendor/github.com/go-redis/redis/result.go
сгенерированный
поставляемый
@@ -1,140 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import "time"
|
|
||||||
|
|
||||||
// NewCmdResult returns a Cmd initialised with val and err for testing
|
|
||||||
func NewCmdResult(val interface{}, err error) *Cmd {
|
|
||||||
var cmd Cmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSliceResult returns a SliceCmd initialised with val and err for testing
|
|
||||||
func NewSliceResult(val []interface{}, err error) *SliceCmd {
|
|
||||||
var cmd SliceCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStatusResult returns a StatusCmd initialised with val and err for testing
|
|
||||||
func NewStatusResult(val string, err error) *StatusCmd {
|
|
||||||
var cmd StatusCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewIntResult returns an IntCmd initialised with val and err for testing
|
|
||||||
func NewIntResult(val int64, err error) *IntCmd {
|
|
||||||
var cmd IntCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewDurationResult returns a DurationCmd initialised with val and err for testing
|
|
||||||
func NewDurationResult(val time.Duration, err error) *DurationCmd {
|
|
||||||
var cmd DurationCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoolResult returns a BoolCmd initialised with val and err for testing
|
|
||||||
func NewBoolResult(val bool, err error) *BoolCmd {
|
|
||||||
var cmd BoolCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringResult returns a StringCmd initialised with val and err for testing
|
|
||||||
func NewStringResult(val string, err error) *StringCmd {
|
|
||||||
var cmd StringCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFloatResult returns a FloatCmd initialised with val and err for testing
|
|
||||||
func NewFloatResult(val float64, err error) *FloatCmd {
|
|
||||||
var cmd FloatCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringSliceResult returns a StringSliceCmd initialised with val and err for testing
|
|
||||||
func NewStringSliceResult(val []string, err error) *StringSliceCmd {
|
|
||||||
var cmd StringSliceCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBoolSliceResult returns a BoolSliceCmd initialised with val and err for testing
|
|
||||||
func NewBoolSliceResult(val []bool, err error) *BoolSliceCmd {
|
|
||||||
var cmd BoolSliceCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringStringMapResult returns a StringStringMapCmd initialised with val and err for testing
|
|
||||||
func NewStringStringMapResult(val map[string]string, err error) *StringStringMapCmd {
|
|
||||||
var cmd StringStringMapCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewStringIntMapCmdResult returns a StringIntMapCmd initialised with val and err for testing
|
|
||||||
func NewStringIntMapCmdResult(val map[string]int64, err error) *StringIntMapCmd {
|
|
||||||
var cmd StringIntMapCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewZSliceCmdResult returns a ZSliceCmd initialised with val and err for testing
|
|
||||||
func NewZSliceCmdResult(val []Z, err error) *ZSliceCmd {
|
|
||||||
var cmd ZSliceCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewScanCmdResult returns a ScanCmd initialised with val and err for testing
|
|
||||||
func NewScanCmdResult(keys []string, cursor uint64, err error) *ScanCmd {
|
|
||||||
var cmd ScanCmd
|
|
||||||
cmd.page = keys
|
|
||||||
cmd.cursor = cursor
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewClusterSlotsCmdResult returns a ClusterSlotsCmd initialised with val and err for testing
|
|
||||||
func NewClusterSlotsCmdResult(val []ClusterSlot, err error) *ClusterSlotsCmd {
|
|
||||||
var cmd ClusterSlotsCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewGeoLocationCmdResult returns a GeoLocationCmd initialised with val and err for testing
|
|
||||||
func NewGeoLocationCmdResult(val []GeoLocation, err error) *GeoLocationCmd {
|
|
||||||
var cmd GeoLocationCmd
|
|
||||||
cmd.locations = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewCommandsInfoCmdResult returns a CommandsInfoCmd initialised with val and err for testing
|
|
||||||
func NewCommandsInfoCmdResult(val map[string]*CommandInfo, err error) *CommandsInfoCmd {
|
|
||||||
var cmd CommandsInfoCmd
|
|
||||||
cmd.val = val
|
|
||||||
cmd.setErr(err)
|
|
||||||
return &cmd
|
|
||||||
}
|
|
||||||
702
vendor/github.com/go-redis/redis/ring.go
сгенерированный
поставляемый
702
vendor/github.com/go-redis/redis/ring.go
сгенерированный
поставляемый
@@ -1,702 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"strconv"
|
|
||||||
"sync"
|
|
||||||
"sync/atomic"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal"
|
|
||||||
"github.com/go-redis/redis/internal/consistenthash"
|
|
||||||
"github.com/go-redis/redis/internal/hashtag"
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Hash is type of hash function used in consistent hash.
|
|
||||||
type Hash consistenthash.Hash
|
|
||||||
|
|
||||||
var errRingShardsDown = errors.New("redis: all ring shards are down")
|
|
||||||
|
|
||||||
// RingOptions are used to configure a ring client and should be
|
|
||||||
// passed to NewRing.
|
|
||||||
type RingOptions struct {
|
|
||||||
// Map of name => host:port addresses of ring shards.
|
|
||||||
Addrs map[string]string
|
|
||||||
|
|
||||||
// Frequency of PING commands sent to check shards availability.
|
|
||||||
// Shard is considered down after 3 subsequent failed checks.
|
|
||||||
HeartbeatFrequency time.Duration
|
|
||||||
|
|
||||||
// Hash function used in consistent hash.
|
|
||||||
// Default is crc32.ChecksumIEEE.
|
|
||||||
Hash Hash
|
|
||||||
|
|
||||||
// Number of replicas in consistent hash.
|
|
||||||
// Default is 100 replicas.
|
|
||||||
//
|
|
||||||
// Higher number of replicas will provide less deviation, that is keys will be
|
|
||||||
// distributed to nodes more evenly.
|
|
||||||
//
|
|
||||||
// Following is deviation for common nreplicas:
|
|
||||||
// --------------------------------------------------------
|
|
||||||
// | nreplicas | standard error | 99% confidence interval |
|
|
||||||
// | 10 | 0.3152 | (0.37, 1.98) |
|
|
||||||
// | 100 | 0.0997 | (0.76, 1.28) |
|
|
||||||
// | 1000 | 0.0316 | (0.92, 1.09) |
|
|
||||||
// --------------------------------------------------------
|
|
||||||
//
|
|
||||||
// See https://arxiv.org/abs/1406.2294 for reference
|
|
||||||
HashReplicas int
|
|
||||||
|
|
||||||
// Following options are copied from Options struct.
|
|
||||||
|
|
||||||
OnConnect func(*Conn) error
|
|
||||||
|
|
||||||
DB int
|
|
||||||
Password string
|
|
||||||
|
|
||||||
MaxRetries int
|
|
||||||
MinRetryBackoff time.Duration
|
|
||||||
MaxRetryBackoff time.Duration
|
|
||||||
|
|
||||||
DialTimeout time.Duration
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
WriteTimeout time.Duration
|
|
||||||
|
|
||||||
PoolSize int
|
|
||||||
MinIdleConns int
|
|
||||||
MaxConnAge time.Duration
|
|
||||||
PoolTimeout time.Duration
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
IdleCheckFrequency time.Duration
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opt *RingOptions) init() {
|
|
||||||
if opt.HeartbeatFrequency == 0 {
|
|
||||||
opt.HeartbeatFrequency = 500 * time.Millisecond
|
|
||||||
}
|
|
||||||
|
|
||||||
if opt.HashReplicas == 0 {
|
|
||||||
opt.HashReplicas = 100
|
|
||||||
}
|
|
||||||
|
|
||||||
switch opt.MinRetryBackoff {
|
|
||||||
case -1:
|
|
||||||
opt.MinRetryBackoff = 0
|
|
||||||
case 0:
|
|
||||||
opt.MinRetryBackoff = 8 * time.Millisecond
|
|
||||||
}
|
|
||||||
switch opt.MaxRetryBackoff {
|
|
||||||
case -1:
|
|
||||||
opt.MaxRetryBackoff = 0
|
|
||||||
case 0:
|
|
||||||
opt.MaxRetryBackoff = 512 * time.Millisecond
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opt *RingOptions) clientOptions() *Options {
|
|
||||||
return &Options{
|
|
||||||
OnConnect: opt.OnConnect,
|
|
||||||
|
|
||||||
DB: opt.DB,
|
|
||||||
Password: opt.Password,
|
|
||||||
|
|
||||||
DialTimeout: opt.DialTimeout,
|
|
||||||
ReadTimeout: opt.ReadTimeout,
|
|
||||||
WriteTimeout: opt.WriteTimeout,
|
|
||||||
|
|
||||||
PoolSize: opt.PoolSize,
|
|
||||||
MinIdleConns: opt.MinIdleConns,
|
|
||||||
MaxConnAge: opt.MaxConnAge,
|
|
||||||
PoolTimeout: opt.PoolTimeout,
|
|
||||||
IdleTimeout: opt.IdleTimeout,
|
|
||||||
IdleCheckFrequency: opt.IdleCheckFrequency,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type ringShard struct {
|
|
||||||
Client *Client
|
|
||||||
down int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shard *ringShard) String() string {
|
|
||||||
var state string
|
|
||||||
if shard.IsUp() {
|
|
||||||
state = "up"
|
|
||||||
} else {
|
|
||||||
state = "down"
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("%s is %s", shard.Client, state)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shard *ringShard) IsDown() bool {
|
|
||||||
const threshold = 3
|
|
||||||
return atomic.LoadInt32(&shard.down) >= threshold
|
|
||||||
}
|
|
||||||
|
|
||||||
func (shard *ringShard) IsUp() bool {
|
|
||||||
return !shard.IsDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Vote votes to set shard state and returns true if state was changed.
|
|
||||||
func (shard *ringShard) Vote(up bool) bool {
|
|
||||||
if up {
|
|
||||||
changed := shard.IsDown()
|
|
||||||
atomic.StoreInt32(&shard.down, 0)
|
|
||||||
return changed
|
|
||||||
}
|
|
||||||
|
|
||||||
if shard.IsDown() {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
atomic.AddInt32(&shard.down, 1)
|
|
||||||
return shard.IsDown()
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type ringShards struct {
|
|
||||||
opt *RingOptions
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
|
||||||
hash *consistenthash.Map
|
|
||||||
shards map[string]*ringShard // read only
|
|
||||||
list []*ringShard // read only
|
|
||||||
len int
|
|
||||||
closed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func newRingShards(opt *RingOptions) *ringShards {
|
|
||||||
return &ringShards{
|
|
||||||
opt: opt,
|
|
||||||
|
|
||||||
hash: newConsistentHash(opt),
|
|
||||||
shards: make(map[string]*ringShard),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) Add(name string, cl *Client) {
|
|
||||||
shard := &ringShard{Client: cl}
|
|
||||||
c.hash.Add(name)
|
|
||||||
c.shards[name] = shard
|
|
||||||
c.list = append(c.list, shard)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) List() []*ringShard {
|
|
||||||
c.mu.RLock()
|
|
||||||
list := c.list
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) Hash(key string) string {
|
|
||||||
c.mu.RLock()
|
|
||||||
hash := c.hash.Get(key)
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) GetByKey(key string) (*ringShard, error) {
|
|
||||||
key = hashtag.Key(key)
|
|
||||||
|
|
||||||
c.mu.RLock()
|
|
||||||
|
|
||||||
if c.closed {
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return nil, pool.ErrClosed
|
|
||||||
}
|
|
||||||
|
|
||||||
hash := c.hash.Get(key)
|
|
||||||
if hash == "" {
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return nil, errRingShardsDown
|
|
||||||
}
|
|
||||||
|
|
||||||
shard := c.shards[hash]
|
|
||||||
c.mu.RUnlock()
|
|
||||||
|
|
||||||
return shard, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) GetByHash(name string) (*ringShard, error) {
|
|
||||||
if name == "" {
|
|
||||||
return c.Random()
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.RLock()
|
|
||||||
shard := c.shards[name]
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return shard, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) Random() (*ringShard, error) {
|
|
||||||
return c.GetByKey(strconv.Itoa(rand.Int()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// heartbeat monitors state of each shard in the ring.
|
|
||||||
func (c *ringShards) Heartbeat(frequency time.Duration) {
|
|
||||||
ticker := time.NewTicker(frequency)
|
|
||||||
defer ticker.Stop()
|
|
||||||
for range ticker.C {
|
|
||||||
var rebalance bool
|
|
||||||
|
|
||||||
c.mu.RLock()
|
|
||||||
|
|
||||||
if c.closed {
|
|
||||||
c.mu.RUnlock()
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
shards := c.list
|
|
||||||
c.mu.RUnlock()
|
|
||||||
|
|
||||||
for _, shard := range shards {
|
|
||||||
err := shard.Client.Ping().Err()
|
|
||||||
if shard.Vote(err == nil || err == pool.ErrPoolTimeout) {
|
|
||||||
internal.Logf("ring shard state changed: %s", shard)
|
|
||||||
rebalance = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if rebalance {
|
|
||||||
c.rebalance()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rebalance removes dead shards from the Ring.
|
|
||||||
func (c *ringShards) rebalance() {
|
|
||||||
c.mu.RLock()
|
|
||||||
shards := c.shards
|
|
||||||
c.mu.RUnlock()
|
|
||||||
|
|
||||||
hash := newConsistentHash(c.opt)
|
|
||||||
var shardsNum int
|
|
||||||
for name, shard := range shards {
|
|
||||||
if shard.IsUp() {
|
|
||||||
hash.Add(name)
|
|
||||||
shardsNum++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
c.hash = hash
|
|
||||||
c.len = shardsNum
|
|
||||||
c.mu.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) Len() int {
|
|
||||||
c.mu.RLock()
|
|
||||||
l := c.len
|
|
||||||
c.mu.RUnlock()
|
|
||||||
return l
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *ringShards) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
if c.closed {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
c.closed = true
|
|
||||||
|
|
||||||
var firstErr error
|
|
||||||
for _, shard := range c.shards {
|
|
||||||
if err := shard.Client.Close(); err != nil && firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c.hash = nil
|
|
||||||
c.shards = nil
|
|
||||||
c.list = nil
|
|
||||||
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// Ring is a Redis client that uses consistent hashing to distribute
|
|
||||||
// keys across multiple Redis servers (shards). It's safe for
|
|
||||||
// concurrent use by multiple goroutines.
|
|
||||||
//
|
|
||||||
// Ring monitors the state of each shard and removes dead shards from
|
|
||||||
// the ring. When a shard comes online it is added back to the ring. This
|
|
||||||
// gives you maximum availability and partition tolerance, but no
|
|
||||||
// consistency between different shards or even clients. Each client
|
|
||||||
// uses shards that are available to the client and does not do any
|
|
||||||
// coordination when shard state is changed.
|
|
||||||
//
|
|
||||||
// Ring should be used when you need multiple Redis servers for caching
|
|
||||||
// and can tolerate losing data when one of the servers dies.
|
|
||||||
// Otherwise you should use Redis Cluster.
|
|
||||||
type Ring struct {
|
|
||||||
cmdable
|
|
||||||
|
|
||||||
ctx context.Context
|
|
||||||
|
|
||||||
opt *RingOptions
|
|
||||||
shards *ringShards
|
|
||||||
cmdsInfoCache *cmdsInfoCache
|
|
||||||
|
|
||||||
process func(Cmder) error
|
|
||||||
processPipeline func([]Cmder) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRing(opt *RingOptions) *Ring {
|
|
||||||
opt.init()
|
|
||||||
|
|
||||||
ring := &Ring{
|
|
||||||
opt: opt,
|
|
||||||
shards: newRingShards(opt),
|
|
||||||
}
|
|
||||||
ring.cmdsInfoCache = newCmdsInfoCache(ring.cmdsInfo)
|
|
||||||
|
|
||||||
ring.process = ring.defaultProcess
|
|
||||||
ring.processPipeline = ring.defaultProcessPipeline
|
|
||||||
|
|
||||||
ring.init()
|
|
||||||
|
|
||||||
for name, addr := range opt.Addrs {
|
|
||||||
clopt := opt.clientOptions()
|
|
||||||
clopt.Addr = addr
|
|
||||||
ring.shards.Add(name, NewClient(clopt))
|
|
||||||
}
|
|
||||||
|
|
||||||
go ring.shards.Heartbeat(opt.HeartbeatFrequency)
|
|
||||||
|
|
||||||
return ring
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) init() {
|
|
||||||
c.cmdable.setProcessor(c.Process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) Context() context.Context {
|
|
||||||
if c.ctx != nil {
|
|
||||||
return c.ctx
|
|
||||||
}
|
|
||||||
return context.Background()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) WithContext(ctx context.Context) *Ring {
|
|
||||||
if ctx == nil {
|
|
||||||
panic("nil context")
|
|
||||||
}
|
|
||||||
c2 := c.clone()
|
|
||||||
c2.ctx = ctx
|
|
||||||
return c2
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) clone() *Ring {
|
|
||||||
cp := *c
|
|
||||||
cp.init()
|
|
||||||
|
|
||||||
return &cp
|
|
||||||
}
|
|
||||||
|
|
||||||
// Options returns read-only Options that were used to create the client.
|
|
||||||
func (c *Ring) Options() *RingOptions {
|
|
||||||
return c.opt
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) retryBackoff(attempt int) time.Duration {
|
|
||||||
return internal.RetryBackoff(attempt, c.opt.MinRetryBackoff, c.opt.MaxRetryBackoff)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PoolStats returns accumulated connection pool stats.
|
|
||||||
func (c *Ring) PoolStats() *PoolStats {
|
|
||||||
shards := c.shards.List()
|
|
||||||
var acc PoolStats
|
|
||||||
for _, shard := range shards {
|
|
||||||
s := shard.Client.connPool.Stats()
|
|
||||||
acc.Hits += s.Hits
|
|
||||||
acc.Misses += s.Misses
|
|
||||||
acc.Timeouts += s.Timeouts
|
|
||||||
acc.TotalConns += s.TotalConns
|
|
||||||
acc.IdleConns += s.IdleConns
|
|
||||||
}
|
|
||||||
return &acc
|
|
||||||
}
|
|
||||||
|
|
||||||
// Len returns the current number of shards in the ring.
|
|
||||||
func (c *Ring) Len() int {
|
|
||||||
return c.shards.Len()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe subscribes the client to the specified channels.
|
|
||||||
func (c *Ring) Subscribe(channels ...string) *PubSub {
|
|
||||||
if len(channels) == 0 {
|
|
||||||
panic("at least one channel is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
shard, err := c.shards.GetByKey(channels[0])
|
|
||||||
if err != nil {
|
|
||||||
// TODO: return PubSub with sticky error
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return shard.Client.Subscribe(channels...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSubscribe subscribes the client to the given patterns.
|
|
||||||
func (c *Ring) PSubscribe(channels ...string) *PubSub {
|
|
||||||
if len(channels) == 0 {
|
|
||||||
panic("at least one channel is required")
|
|
||||||
}
|
|
||||||
|
|
||||||
shard, err := c.shards.GetByKey(channels[0])
|
|
||||||
if err != nil {
|
|
||||||
// TODO: return PubSub with sticky error
|
|
||||||
panic(err)
|
|
||||||
}
|
|
||||||
return shard.Client.PSubscribe(channels...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ForEachShard concurrently calls the fn on each live shard in the ring.
|
|
||||||
// It returns the first error if any.
|
|
||||||
func (c *Ring) ForEachShard(fn func(client *Client) error) error {
|
|
||||||
shards := c.shards.List()
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
errCh := make(chan error, 1)
|
|
||||||
for _, shard := range shards {
|
|
||||||
if shard.IsDown() {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Add(1)
|
|
||||||
go func(shard *ringShard) {
|
|
||||||
defer wg.Done()
|
|
||||||
err := fn(shard.Client)
|
|
||||||
if err != nil {
|
|
||||||
select {
|
|
||||||
case errCh <- err:
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(shard)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case err := <-errCh:
|
|
||||||
return err
|
|
||||||
default:
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) cmdsInfo() (map[string]*CommandInfo, error) {
|
|
||||||
shards := c.shards.List()
|
|
||||||
firstErr := errRingShardsDown
|
|
||||||
for _, shard := range shards {
|
|
||||||
cmdsInfo, err := shard.Client.Command().Result()
|
|
||||||
if err == nil {
|
|
||||||
return cmdsInfo, nil
|
|
||||||
}
|
|
||||||
if firstErr == nil {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil, firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) cmdInfo(name string) *CommandInfo {
|
|
||||||
cmdsInfo, err := c.cmdsInfoCache.Get()
|
|
||||||
if err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
info := cmdsInfo[name]
|
|
||||||
if info == nil {
|
|
||||||
internal.Logf("info for cmd=%s not found", name)
|
|
||||||
}
|
|
||||||
return info
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) cmdShard(cmd Cmder) (*ringShard, error) {
|
|
||||||
cmdInfo := c.cmdInfo(cmd.Name())
|
|
||||||
pos := cmdFirstKeyPos(cmd, cmdInfo)
|
|
||||||
if pos == 0 {
|
|
||||||
return c.shards.Random()
|
|
||||||
}
|
|
||||||
firstKey := cmd.stringArg(pos)
|
|
||||||
return c.shards.GetByKey(firstKey)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do creates a Cmd from the args and processes the cmd.
|
|
||||||
func (c *Ring) Do(args ...interface{}) *Cmd {
|
|
||||||
cmd := NewCmd(args...)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) WrapProcess(
|
|
||||||
fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error,
|
|
||||||
) {
|
|
||||||
c.process = fn(c.process)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) Process(cmd Cmder) error {
|
|
||||||
return c.process(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) defaultProcess(cmd Cmder) error {
|
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
|
||||||
if attempt > 0 {
|
|
||||||
time.Sleep(c.retryBackoff(attempt))
|
|
||||||
}
|
|
||||||
|
|
||||||
shard, err := c.cmdShard(cmd)
|
|
||||||
if err != nil {
|
|
||||||
cmd.setErr(err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = shard.Client.Process(cmd)
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
if !internal.IsRetryableError(err, cmd.readTimeout() == nil) {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cmd.Err()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) Pipeline() Pipeliner {
|
|
||||||
pipe := Pipeline{
|
|
||||||
exec: c.processPipeline,
|
|
||||||
}
|
|
||||||
pipe.cmdable.setProcessor(pipe.Process)
|
|
||||||
return &pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.Pipeline().Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) WrapProcessPipeline(
|
|
||||||
fn func(oldProcess func([]Cmder) error) func([]Cmder) error,
|
|
||||||
) {
|
|
||||||
c.processPipeline = fn(c.processPipeline)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) defaultProcessPipeline(cmds []Cmder) error {
|
|
||||||
cmdsMap := make(map[string][]Cmder)
|
|
||||||
for _, cmd := range cmds {
|
|
||||||
cmdInfo := c.cmdInfo(cmd.Name())
|
|
||||||
hash := cmd.stringArg(cmdFirstKeyPos(cmd, cmdInfo))
|
|
||||||
if hash != "" {
|
|
||||||
hash = c.shards.Hash(hashtag.Key(hash))
|
|
||||||
}
|
|
||||||
cmdsMap[hash] = append(cmdsMap[hash], cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
for attempt := 0; attempt <= c.opt.MaxRetries; attempt++ {
|
|
||||||
if attempt > 0 {
|
|
||||||
time.Sleep(c.retryBackoff(attempt))
|
|
||||||
}
|
|
||||||
|
|
||||||
var mu sync.Mutex
|
|
||||||
var failedCmdsMap map[string][]Cmder
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
|
|
||||||
for hash, cmds := range cmdsMap {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(hash string, cmds []Cmder) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
shard, err := c.shards.GetByHash(hash)
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
cn, err := shard.Client.getConn()
|
|
||||||
if err != nil {
|
|
||||||
setCmdsErr(cmds, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
canRetry, err := shard.Client.pipelineProcessCmds(cn, cmds)
|
|
||||||
shard.Client.releaseConnStrict(cn, err)
|
|
||||||
|
|
||||||
if canRetry && internal.IsRetryableError(err, true) {
|
|
||||||
mu.Lock()
|
|
||||||
if failedCmdsMap == nil {
|
|
||||||
failedCmdsMap = make(map[string][]Cmder)
|
|
||||||
}
|
|
||||||
failedCmdsMap[hash] = cmds
|
|
||||||
mu.Unlock()
|
|
||||||
}
|
|
||||||
}(hash, cmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
if len(failedCmdsMap) == 0 {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
cmdsMap = failedCmdsMap
|
|
||||||
}
|
|
||||||
|
|
||||||
return cmdsFirstErr(cmds)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) TxPipeline() Pipeliner {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the ring client, releasing any open resources.
|
|
||||||
//
|
|
||||||
// It is rare to Close a Ring, as the Ring is meant to be long-lived
|
|
||||||
// and shared between many goroutines.
|
|
||||||
func (c *Ring) Close() error {
|
|
||||||
return c.shards.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Ring) Watch(fn func(*Tx) error, keys ...string) error {
|
|
||||||
if len(keys) == 0 {
|
|
||||||
return fmt.Errorf("redis: Watch requires at least one key")
|
|
||||||
}
|
|
||||||
|
|
||||||
var shards []*ringShard
|
|
||||||
for _, key := range keys {
|
|
||||||
if key != "" {
|
|
||||||
shard, err := c.shards.GetByKey(hashtag.Key(key))
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
shards = append(shards, shard)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(shards) == 0 {
|
|
||||||
return fmt.Errorf("redis: Watch requires at least one shard")
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(shards) > 1 {
|
|
||||||
for _, shard := range shards[1:] {
|
|
||||||
if shard.Client != shards[0].Client {
|
|
||||||
err := fmt.Errorf("redis: Watch requires all keys to be in the same shard")
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return shards[0].Client.Watch(fn, keys...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConsistentHash(opt *RingOptions) *consistenthash.Map {
|
|
||||||
return consistenthash.New(opt.HashReplicas, consistenthash.Hash(opt.Hash))
|
|
||||||
}
|
|
||||||
62
vendor/github.com/go-redis/redis/script.go
сгенерированный
поставляемый
62
vendor/github.com/go-redis/redis/script.go
сгенерированный
поставляемый
@@ -1,62 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha1"
|
|
||||||
"encoding/hex"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type scripter interface {
|
|
||||||
Eval(script string, keys []string, args ...interface{}) *Cmd
|
|
||||||
EvalSha(sha1 string, keys []string, args ...interface{}) *Cmd
|
|
||||||
ScriptExists(hashes ...string) *BoolSliceCmd
|
|
||||||
ScriptLoad(script string) *StringCmd
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ scripter = (*Client)(nil)
|
|
||||||
var _ scripter = (*Ring)(nil)
|
|
||||||
var _ scripter = (*ClusterClient)(nil)
|
|
||||||
|
|
||||||
type Script struct {
|
|
||||||
src, hash string
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewScript(src string) *Script {
|
|
||||||
h := sha1.New()
|
|
||||||
io.WriteString(h, src)
|
|
||||||
return &Script{
|
|
||||||
src: src,
|
|
||||||
hash: hex.EncodeToString(h.Sum(nil)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Script) Hash() string {
|
|
||||||
return s.hash
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Script) Load(c scripter) *StringCmd {
|
|
||||||
return c.ScriptLoad(s.src)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Script) Exists(c scripter) *BoolSliceCmd {
|
|
||||||
return c.ScriptExists(s.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Script) Eval(c scripter, keys []string, args ...interface{}) *Cmd {
|
|
||||||
return c.Eval(s.src, keys, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Script) EvalSha(c scripter, keys []string, args ...interface{}) *Cmd {
|
|
||||||
return c.EvalSha(s.hash, keys, args...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Run optimistically uses EVALSHA to run the script. If script does not exist
|
|
||||||
// it is retried using EVAL.
|
|
||||||
func (s *Script) Run(c scripter, keys []string, args ...interface{}) *Cmd {
|
|
||||||
r := s.EvalSha(c, keys, args...)
|
|
||||||
if err := r.Err(); err != nil && strings.HasPrefix(err.Error(), "NOSCRIPT ") {
|
|
||||||
return s.Eval(c, keys, args...)
|
|
||||||
}
|
|
||||||
return r
|
|
||||||
}
|
|
||||||
411
vendor/github.com/go-redis/redis/sentinel.go
сгенерированный
поставляемый
411
vendor/github.com/go-redis/redis/sentinel.go
сгенерированный
поставляемый
@@ -1,411 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"errors"
|
|
||||||
"net"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/go-redis/redis/internal"
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
)
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
// FailoverOptions are used to configure a failover client and should
|
|
||||||
// be passed to NewFailoverClient.
|
|
||||||
type FailoverOptions struct {
|
|
||||||
// The master name.
|
|
||||||
MasterName string
|
|
||||||
// A seed list of host:port addresses of sentinel nodes.
|
|
||||||
SentinelAddrs []string
|
|
||||||
|
|
||||||
// Following options are copied from Options struct.
|
|
||||||
|
|
||||||
OnConnect func(*Conn) error
|
|
||||||
|
|
||||||
Password string
|
|
||||||
DB int
|
|
||||||
|
|
||||||
MaxRetries int
|
|
||||||
MinRetryBackoff time.Duration
|
|
||||||
MaxRetryBackoff time.Duration
|
|
||||||
|
|
||||||
DialTimeout time.Duration
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
WriteTimeout time.Duration
|
|
||||||
|
|
||||||
PoolSize int
|
|
||||||
MinIdleConns int
|
|
||||||
MaxConnAge time.Duration
|
|
||||||
PoolTimeout time.Duration
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
IdleCheckFrequency time.Duration
|
|
||||||
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
}
|
|
||||||
|
|
||||||
func (opt *FailoverOptions) options() *Options {
|
|
||||||
return &Options{
|
|
||||||
Addr: "FailoverClient",
|
|
||||||
|
|
||||||
OnConnect: opt.OnConnect,
|
|
||||||
|
|
||||||
DB: opt.DB,
|
|
||||||
Password: opt.Password,
|
|
||||||
|
|
||||||
MaxRetries: opt.MaxRetries,
|
|
||||||
|
|
||||||
DialTimeout: opt.DialTimeout,
|
|
||||||
ReadTimeout: opt.ReadTimeout,
|
|
||||||
WriteTimeout: opt.WriteTimeout,
|
|
||||||
|
|
||||||
PoolSize: opt.PoolSize,
|
|
||||||
PoolTimeout: opt.PoolTimeout,
|
|
||||||
IdleTimeout: opt.IdleTimeout,
|
|
||||||
IdleCheckFrequency: opt.IdleCheckFrequency,
|
|
||||||
|
|
||||||
TLSConfig: opt.TLSConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFailoverClient returns a Redis client that uses Redis Sentinel
|
|
||||||
// for automatic failover. It's safe for concurrent use by multiple
|
|
||||||
// goroutines.
|
|
||||||
func NewFailoverClient(failoverOpt *FailoverOptions) *Client {
|
|
||||||
opt := failoverOpt.options()
|
|
||||||
opt.init()
|
|
||||||
|
|
||||||
failover := &sentinelFailover{
|
|
||||||
masterName: failoverOpt.MasterName,
|
|
||||||
sentinelAddrs: failoverOpt.SentinelAddrs,
|
|
||||||
|
|
||||||
opt: opt,
|
|
||||||
}
|
|
||||||
|
|
||||||
c := Client{
|
|
||||||
baseClient: baseClient{
|
|
||||||
opt: opt,
|
|
||||||
connPool: failover.Pool(),
|
|
||||||
|
|
||||||
onClose: failover.Close,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.baseClient.init()
|
|
||||||
c.cmdable.setProcessor(c.Process)
|
|
||||||
|
|
||||||
return &c
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------------------------------------
|
|
||||||
|
|
||||||
type SentinelClient struct {
|
|
||||||
baseClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSentinelClient(opt *Options) *SentinelClient {
|
|
||||||
opt.init()
|
|
||||||
c := &SentinelClient{
|
|
||||||
baseClient: baseClient{
|
|
||||||
opt: opt,
|
|
||||||
connPool: newConnPool(opt),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
c.baseClient.init()
|
|
||||||
return c
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SentinelClient) pubSub() *PubSub {
|
|
||||||
pubsub := &PubSub{
|
|
||||||
opt: c.opt,
|
|
||||||
|
|
||||||
newConn: func(channels []string) (*pool.Conn, error) {
|
|
||||||
return c.newConn()
|
|
||||||
},
|
|
||||||
closeConn: c.connPool.CloseConn,
|
|
||||||
}
|
|
||||||
pubsub.init()
|
|
||||||
return pubsub
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subscribe subscribes the client to the specified channels.
|
|
||||||
// Channels can be omitted to create empty subscription.
|
|
||||||
func (c *SentinelClient) Subscribe(channels ...string) *PubSub {
|
|
||||||
pubsub := c.pubSub()
|
|
||||||
if len(channels) > 0 {
|
|
||||||
_ = pubsub.Subscribe(channels...)
|
|
||||||
}
|
|
||||||
return pubsub
|
|
||||||
}
|
|
||||||
|
|
||||||
// PSubscribe subscribes the client to the given patterns.
|
|
||||||
// Patterns can be omitted to create empty subscription.
|
|
||||||
func (c *SentinelClient) PSubscribe(channels ...string) *PubSub {
|
|
||||||
pubsub := c.pubSub()
|
|
||||||
if len(channels) > 0 {
|
|
||||||
_ = pubsub.PSubscribe(channels...)
|
|
||||||
}
|
|
||||||
return pubsub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SentinelClient) GetMasterAddrByName(name string) *StringSliceCmd {
|
|
||||||
cmd := NewStringSliceCmd("sentinel", "get-master-addr-by-name", name)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *SentinelClient) Sentinels(name string) *SliceCmd {
|
|
||||||
cmd := NewSliceCmd("sentinel", "sentinels", name)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failover forces a failover as if the master was not reachable, and without
|
|
||||||
// asking for agreement to other Sentinels.
|
|
||||||
func (c *SentinelClient) Failover(name string) *StatusCmd {
|
|
||||||
cmd := NewStatusCmd("sentinel", "failover", name)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset resets all the masters with matching name. The pattern argument is a
|
|
||||||
// glob-style pattern. The reset process clears any previous state in a master
|
|
||||||
// (including a failover in progress), and removes every slave and sentinel
|
|
||||||
// already discovered and associated with the master.
|
|
||||||
func (c *SentinelClient) Reset(pattern string) *IntCmd {
|
|
||||||
cmd := NewIntCmd("sentinel", "reset", pattern)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// FlushConfig forces Sentinel to rewrite its configuration on disk, including
|
|
||||||
// the current Sentinel state.
|
|
||||||
func (c *SentinelClient) FlushConfig() *StatusCmd {
|
|
||||||
cmd := NewStatusCmd("sentinel", "flushconfig")
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Master shows the state and info of the specified master.
|
|
||||||
func (c *SentinelClient) Master(name string) *StringStringMapCmd {
|
|
||||||
cmd := NewStringStringMapCmd("sentinel", "master", name)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
type sentinelFailover struct {
|
|
||||||
sentinelAddrs []string
|
|
||||||
|
|
||||||
opt *Options
|
|
||||||
|
|
||||||
pool *pool.ConnPool
|
|
||||||
poolOnce sync.Once
|
|
||||||
|
|
||||||
mu sync.RWMutex
|
|
||||||
masterName string
|
|
||||||
_masterAddr string
|
|
||||||
sentinel *SentinelClient
|
|
||||||
pubsub *PubSub
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) Close() error {
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
if c.sentinel != nil {
|
|
||||||
return c.closeSentinel()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) Pool() *pool.ConnPool {
|
|
||||||
c.poolOnce.Do(func() {
|
|
||||||
c.opt.Dialer = c.dial
|
|
||||||
c.pool = newConnPool(c.opt)
|
|
||||||
})
|
|
||||||
return c.pool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) dial() (net.Conn, error) {
|
|
||||||
addr, err := c.MasterAddr()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return net.DialTimeout("tcp", addr, c.opt.DialTimeout)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) MasterAddr() (string, error) {
|
|
||||||
addr, err := c.masterAddr()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
c.switchMaster(addr)
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) masterAddr() (string, error) {
|
|
||||||
addr := c.getMasterAddr()
|
|
||||||
if addr != "" {
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
for i, sentinelAddr := range c.sentinelAddrs {
|
|
||||||
sentinel := NewSentinelClient(&Options{
|
|
||||||
Addr: sentinelAddr,
|
|
||||||
|
|
||||||
MaxRetries: c.opt.MaxRetries,
|
|
||||||
|
|
||||||
DialTimeout: c.opt.DialTimeout,
|
|
||||||
ReadTimeout: c.opt.ReadTimeout,
|
|
||||||
WriteTimeout: c.opt.WriteTimeout,
|
|
||||||
|
|
||||||
PoolSize: c.opt.PoolSize,
|
|
||||||
PoolTimeout: c.opt.PoolTimeout,
|
|
||||||
IdleTimeout: c.opt.IdleTimeout,
|
|
||||||
IdleCheckFrequency: c.opt.IdleCheckFrequency,
|
|
||||||
|
|
||||||
TLSConfig: c.opt.TLSConfig,
|
|
||||||
})
|
|
||||||
|
|
||||||
masterAddr, err := sentinel.GetMasterAddrByName(c.masterName).Result()
|
|
||||||
if err != nil {
|
|
||||||
internal.Logf("sentinel: GetMasterAddrByName master=%q failed: %s",
|
|
||||||
c.masterName, err)
|
|
||||||
_ = sentinel.Close()
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Push working sentinel to the top.
|
|
||||||
c.sentinelAddrs[0], c.sentinelAddrs[i] = c.sentinelAddrs[i], c.sentinelAddrs[0]
|
|
||||||
c.setSentinel(sentinel)
|
|
||||||
|
|
||||||
addr := net.JoinHostPort(masterAddr[0], masterAddr[1])
|
|
||||||
return addr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return "", errors.New("redis: all sentinels are unreachable")
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) getMasterAddr() string {
|
|
||||||
c.mu.RLock()
|
|
||||||
sentinel := c.sentinel
|
|
||||||
c.mu.RUnlock()
|
|
||||||
|
|
||||||
if sentinel == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
addr, err := sentinel.GetMasterAddrByName(c.masterName).Result()
|
|
||||||
if err != nil {
|
|
||||||
internal.Logf("sentinel: GetMasterAddrByName name=%q failed: %s",
|
|
||||||
c.masterName, err)
|
|
||||||
c.mu.Lock()
|
|
||||||
if c.sentinel == sentinel {
|
|
||||||
c.closeSentinel()
|
|
||||||
}
|
|
||||||
c.mu.Unlock()
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
return net.JoinHostPort(addr[0], addr[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) switchMaster(addr string) {
|
|
||||||
c.mu.RLock()
|
|
||||||
masterAddr := c._masterAddr
|
|
||||||
c.mu.RUnlock()
|
|
||||||
if masterAddr == addr {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
c.mu.Lock()
|
|
||||||
defer c.mu.Unlock()
|
|
||||||
|
|
||||||
internal.Logf("sentinel: new master=%q addr=%q",
|
|
||||||
c.masterName, addr)
|
|
||||||
_ = c.Pool().Filter(func(cn *pool.Conn) bool {
|
|
||||||
return cn.RemoteAddr().String() != addr
|
|
||||||
})
|
|
||||||
c._masterAddr = addr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) setSentinel(sentinel *SentinelClient) {
|
|
||||||
c.discoverSentinels(sentinel)
|
|
||||||
c.sentinel = sentinel
|
|
||||||
|
|
||||||
c.pubsub = sentinel.Subscribe("+switch-master")
|
|
||||||
go c.listen(c.pubsub)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) closeSentinel() error {
|
|
||||||
var firstErr error
|
|
||||||
|
|
||||||
err := c.pubsub.Close()
|
|
||||||
if err != nil && firstErr == err {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
c.pubsub = nil
|
|
||||||
|
|
||||||
err = c.sentinel.Close()
|
|
||||||
if err != nil && firstErr == err {
|
|
||||||
firstErr = err
|
|
||||||
}
|
|
||||||
c.sentinel = nil
|
|
||||||
|
|
||||||
return firstErr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) discoverSentinels(sentinel *SentinelClient) {
|
|
||||||
sentinels, err := sentinel.Sentinels(c.masterName).Result()
|
|
||||||
if err != nil {
|
|
||||||
internal.Logf("sentinel: Sentinels master=%q failed: %s", c.masterName, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for _, sentinel := range sentinels {
|
|
||||||
vals := sentinel.([]interface{})
|
|
||||||
for i := 0; i < len(vals); i += 2 {
|
|
||||||
key := vals[i].(string)
|
|
||||||
if key == "name" {
|
|
||||||
sentinelAddr := vals[i+1].(string)
|
|
||||||
if !contains(c.sentinelAddrs, sentinelAddr) {
|
|
||||||
internal.Logf("sentinel: discovered new sentinel=%q for master=%q",
|
|
||||||
sentinelAddr, c.masterName)
|
|
||||||
c.sentinelAddrs = append(c.sentinelAddrs, sentinelAddr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *sentinelFailover) listen(pubsub *PubSub) {
|
|
||||||
ch := pubsub.Channel()
|
|
||||||
for {
|
|
||||||
msg, ok := <-ch
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.Channel == "+switch-master" {
|
|
||||||
parts := strings.Split(msg.Payload, " ")
|
|
||||||
if parts[0] != c.masterName {
|
|
||||||
internal.Logf("sentinel: ignore addr for master=%q", parts[0])
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
addr := net.JoinHostPort(parts[3], parts[4])
|
|
||||||
c.switchMaster(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func contains(slice []string, str string) bool {
|
|
||||||
for _, s := range slice {
|
|
||||||
if s == str {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
110
vendor/github.com/go-redis/redis/tx.go
сгенерированный
поставляемый
110
vendor/github.com/go-redis/redis/tx.go
сгенерированный
поставляемый
@@ -1,110 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/go-redis/redis/internal/pool"
|
|
||||||
"github.com/go-redis/redis/internal/proto"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TxFailedErr transaction redis failed.
|
|
||||||
const TxFailedErr = proto.RedisError("redis: transaction failed")
|
|
||||||
|
|
||||||
// Tx implements Redis transactions as described in
|
|
||||||
// http://redis.io/topics/transactions. It's NOT safe for concurrent use
|
|
||||||
// by multiple goroutines, because Exec resets list of watched keys.
|
|
||||||
// If you don't need WATCH it is better to use Pipeline.
|
|
||||||
type Tx struct {
|
|
||||||
statefulCmdable
|
|
||||||
baseClient
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Client) newTx() *Tx {
|
|
||||||
tx := Tx{
|
|
||||||
baseClient: baseClient{
|
|
||||||
opt: c.opt,
|
|
||||||
connPool: pool.NewStickyConnPool(c.connPool.(*pool.ConnPool), true),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
tx.baseClient.init()
|
|
||||||
tx.statefulCmdable.setProcessor(tx.Process)
|
|
||||||
return &tx
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch prepares a transaction and marks the keys to be watched
|
|
||||||
// for conditional execution if there are any keys.
|
|
||||||
//
|
|
||||||
// The transaction is automatically closed when fn exits.
|
|
||||||
func (c *Client) Watch(fn func(*Tx) error, keys ...string) error {
|
|
||||||
tx := c.newTx()
|
|
||||||
if len(keys) > 0 {
|
|
||||||
if err := tx.Watch(keys...).Err(); err != nil {
|
|
||||||
_ = tx.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
err := fn(tx)
|
|
||||||
_ = tx.Close()
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Close closes the transaction, releasing any open resources.
|
|
||||||
func (c *Tx) Close() error {
|
|
||||||
_ = c.Unwatch().Err()
|
|
||||||
return c.baseClient.Close()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Watch marks the keys to be watched for conditional execution
|
|
||||||
// of a transaction.
|
|
||||||
func (c *Tx) Watch(keys ...string) *StatusCmd {
|
|
||||||
args := make([]interface{}, 1+len(keys))
|
|
||||||
args[0] = "watch"
|
|
||||||
for i, key := range keys {
|
|
||||||
args[1+i] = key
|
|
||||||
}
|
|
||||||
cmd := NewStatusCmd(args...)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unwatch flushes all the previously watched keys for a transaction.
|
|
||||||
func (c *Tx) Unwatch(keys ...string) *StatusCmd {
|
|
||||||
args := make([]interface{}, 1+len(keys))
|
|
||||||
args[0] = "unwatch"
|
|
||||||
for i, key := range keys {
|
|
||||||
args[1+i] = key
|
|
||||||
}
|
|
||||||
cmd := NewStatusCmd(args...)
|
|
||||||
c.Process(cmd)
|
|
||||||
return cmd
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipeline creates a new pipeline. It is more convenient to use Pipelined.
|
|
||||||
func (c *Tx) Pipeline() Pipeliner {
|
|
||||||
pipe := Pipeline{
|
|
||||||
exec: c.processTxPipeline,
|
|
||||||
}
|
|
||||||
pipe.statefulCmdable.setProcessor(pipe.Process)
|
|
||||||
return &pipe
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pipelined executes commands queued in the fn in a transaction.
|
|
||||||
//
|
|
||||||
// When using WATCH, EXEC will execute commands only if the watched keys
|
|
||||||
// were not modified, allowing for a check-and-set mechanism.
|
|
||||||
//
|
|
||||||
// Exec always returns list of commands. If transaction fails
|
|
||||||
// TxFailedErr is returned. Otherwise Exec returns an error of the first
|
|
||||||
// failed command or nil.
|
|
||||||
func (c *Tx) Pipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.Pipeline().Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxPipelined is an alias for Pipelined.
|
|
||||||
func (c *Tx) TxPipelined(fn func(Pipeliner) error) ([]Cmder, error) {
|
|
||||||
return c.Pipelined(fn)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TxPipeline is an alias for Pipeline.
|
|
||||||
func (c *Tx) TxPipeline() Pipeliner {
|
|
||||||
return c.Pipeline()
|
|
||||||
}
|
|
||||||
180
vendor/github.com/go-redis/redis/universal.go
сгенерированный
поставляемый
180
vendor/github.com/go-redis/redis/universal.go
сгенерированный
поставляемый
@@ -1,180 +0,0 @@
|
|||||||
package redis
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/tls"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
// UniversalOptions information is required by UniversalClient to establish
|
|
||||||
// connections.
|
|
||||||
type UniversalOptions struct {
|
|
||||||
// Either a single address or a seed list of host:port addresses
|
|
||||||
// of cluster/sentinel nodes.
|
|
||||||
Addrs []string
|
|
||||||
|
|
||||||
// Database to be selected after connecting to the server.
|
|
||||||
// Only single-node and failover clients.
|
|
||||||
DB int
|
|
||||||
|
|
||||||
// Common options.
|
|
||||||
|
|
||||||
OnConnect func(*Conn) error
|
|
||||||
Password string
|
|
||||||
MaxRetries int
|
|
||||||
MinRetryBackoff time.Duration
|
|
||||||
MaxRetryBackoff time.Duration
|
|
||||||
DialTimeout time.Duration
|
|
||||||
ReadTimeout time.Duration
|
|
||||||
WriteTimeout time.Duration
|
|
||||||
PoolSize int
|
|
||||||
MinIdleConns int
|
|
||||||
MaxConnAge time.Duration
|
|
||||||
PoolTimeout time.Duration
|
|
||||||
IdleTimeout time.Duration
|
|
||||||
IdleCheckFrequency time.Duration
|
|
||||||
TLSConfig *tls.Config
|
|
||||||
|
|
||||||
// Only cluster clients.
|
|
||||||
|
|
||||||
MaxRedirects int
|
|
||||||
ReadOnly bool
|
|
||||||
RouteByLatency bool
|
|
||||||
RouteRandomly bool
|
|
||||||
|
|
||||||
// The sentinel master name.
|
|
||||||
// Only failover clients.
|
|
||||||
MasterName string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *UniversalOptions) cluster() *ClusterOptions {
|
|
||||||
if len(o.Addrs) == 0 {
|
|
||||||
o.Addrs = []string{"127.0.0.1:6379"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &ClusterOptions{
|
|
||||||
Addrs: o.Addrs,
|
|
||||||
OnConnect: o.OnConnect,
|
|
||||||
|
|
||||||
Password: o.Password,
|
|
||||||
|
|
||||||
MaxRedirects: o.MaxRedirects,
|
|
||||||
ReadOnly: o.ReadOnly,
|
|
||||||
RouteByLatency: o.RouteByLatency,
|
|
||||||
RouteRandomly: o.RouteRandomly,
|
|
||||||
|
|
||||||
MaxRetries: o.MaxRetries,
|
|
||||||
MinRetryBackoff: o.MinRetryBackoff,
|
|
||||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
|
||||||
|
|
||||||
DialTimeout: o.DialTimeout,
|
|
||||||
ReadTimeout: o.ReadTimeout,
|
|
||||||
WriteTimeout: o.WriteTimeout,
|
|
||||||
PoolSize: o.PoolSize,
|
|
||||||
MinIdleConns: o.MinIdleConns,
|
|
||||||
MaxConnAge: o.MaxConnAge,
|
|
||||||
PoolTimeout: o.PoolTimeout,
|
|
||||||
IdleTimeout: o.IdleTimeout,
|
|
||||||
IdleCheckFrequency: o.IdleCheckFrequency,
|
|
||||||
|
|
||||||
TLSConfig: o.TLSConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *UniversalOptions) failover() *FailoverOptions {
|
|
||||||
if len(o.Addrs) == 0 {
|
|
||||||
o.Addrs = []string{"127.0.0.1:26379"}
|
|
||||||
}
|
|
||||||
|
|
||||||
return &FailoverOptions{
|
|
||||||
SentinelAddrs: o.Addrs,
|
|
||||||
MasterName: o.MasterName,
|
|
||||||
OnConnect: o.OnConnect,
|
|
||||||
|
|
||||||
DB: o.DB,
|
|
||||||
Password: o.Password,
|
|
||||||
|
|
||||||
MaxRetries: o.MaxRetries,
|
|
||||||
MinRetryBackoff: o.MinRetryBackoff,
|
|
||||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
|
||||||
|
|
||||||
DialTimeout: o.DialTimeout,
|
|
||||||
ReadTimeout: o.ReadTimeout,
|
|
||||||
WriteTimeout: o.WriteTimeout,
|
|
||||||
|
|
||||||
PoolSize: o.PoolSize,
|
|
||||||
MinIdleConns: o.MinIdleConns,
|
|
||||||
MaxConnAge: o.MaxConnAge,
|
|
||||||
PoolTimeout: o.PoolTimeout,
|
|
||||||
IdleTimeout: o.IdleTimeout,
|
|
||||||
IdleCheckFrequency: o.IdleCheckFrequency,
|
|
||||||
|
|
||||||
TLSConfig: o.TLSConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *UniversalOptions) simple() *Options {
|
|
||||||
addr := "127.0.0.1:6379"
|
|
||||||
if len(o.Addrs) > 0 {
|
|
||||||
addr = o.Addrs[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
return &Options{
|
|
||||||
Addr: addr,
|
|
||||||
OnConnect: o.OnConnect,
|
|
||||||
|
|
||||||
DB: o.DB,
|
|
||||||
Password: o.Password,
|
|
||||||
|
|
||||||
MaxRetries: o.MaxRetries,
|
|
||||||
MinRetryBackoff: o.MinRetryBackoff,
|
|
||||||
MaxRetryBackoff: o.MaxRetryBackoff,
|
|
||||||
|
|
||||||
DialTimeout: o.DialTimeout,
|
|
||||||
ReadTimeout: o.ReadTimeout,
|
|
||||||
WriteTimeout: o.WriteTimeout,
|
|
||||||
|
|
||||||
PoolSize: o.PoolSize,
|
|
||||||
MinIdleConns: o.MinIdleConns,
|
|
||||||
MaxConnAge: o.MaxConnAge,
|
|
||||||
PoolTimeout: o.PoolTimeout,
|
|
||||||
IdleTimeout: o.IdleTimeout,
|
|
||||||
IdleCheckFrequency: o.IdleCheckFrequency,
|
|
||||||
|
|
||||||
TLSConfig: o.TLSConfig,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// --------------------------------------------------------------------
|
|
||||||
|
|
||||||
// UniversalClient is an abstract client which - based on the provided options -
|
|
||||||
// can connect to either clusters, or sentinel-backed failover instances or simple
|
|
||||||
// single-instance servers. This can be useful for testing cluster-specific
|
|
||||||
// applications locally.
|
|
||||||
type UniversalClient interface {
|
|
||||||
Cmdable
|
|
||||||
Watch(fn func(*Tx) error, keys ...string) error
|
|
||||||
Process(cmd Cmder) error
|
|
||||||
WrapProcess(fn func(oldProcess func(cmd Cmder) error) func(cmd Cmder) error)
|
|
||||||
WrapProcessPipeline(fn func(oldProcess func([]Cmder) error) func([]Cmder) error)
|
|
||||||
Subscribe(channels ...string) *PubSub
|
|
||||||
PSubscribe(channels ...string) *PubSub
|
|
||||||
Close() error
|
|
||||||
}
|
|
||||||
|
|
||||||
var _ UniversalClient = (*Client)(nil)
|
|
||||||
var _ UniversalClient = (*ClusterClient)(nil)
|
|
||||||
|
|
||||||
// NewUniversalClient returns a new multi client. The type of client returned depends
|
|
||||||
// on the following three conditions:
|
|
||||||
//
|
|
||||||
// 1. if a MasterName is passed a sentinel-backed FailoverClient will be returned
|
|
||||||
// 2. if the number of Addrs is two or more, a ClusterClient will be returned
|
|
||||||
// 3. otherwise, a single-node redis Client will be returned.
|
|
||||||
func NewUniversalClient(opts *UniversalOptions) UniversalClient {
|
|
||||||
if opts.MasterName != "" {
|
|
||||||
return NewFailoverClient(opts.failover())
|
|
||||||
} else if len(opts.Addrs) > 1 {
|
|
||||||
return NewClusterClient(opts.cluster())
|
|
||||||
}
|
|
||||||
return NewClient(opts.simple())
|
|
||||||
}
|
|
||||||
8
vendor/modules.txt
поставляемый
8
vendor/modules.txt
поставляемый
@@ -22,14 +22,6 @@ github.com/disintegration/imaging
|
|||||||
github.com/dyatlov/go-opengraph/opengraph
|
github.com/dyatlov/go-opengraph/opengraph
|
||||||
# github.com/fsnotify/fsnotify v1.4.7
|
# github.com/fsnotify/fsnotify v1.4.7
|
||||||
github.com/fsnotify/fsnotify
|
github.com/fsnotify/fsnotify
|
||||||
# github.com/go-redis/redis v6.15.5+incompatible
|
|
||||||
github.com/go-redis/redis
|
|
||||||
github.com/go-redis/redis/internal
|
|
||||||
github.com/go-redis/redis/internal/consistenthash
|
|
||||||
github.com/go-redis/redis/internal/hashtag
|
|
||||||
github.com/go-redis/redis/internal/pool
|
|
||||||
github.com/go-redis/redis/internal/proto
|
|
||||||
github.com/go-redis/redis/internal/util
|
|
||||||
# github.com/go-sql-driver/mysql v1.4.1
|
# github.com/go-sql-driver/mysql v1.4.1
|
||||||
github.com/go-sql-driver/mysql
|
github.com/go-sql-driver/mysql
|
||||||
# github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
# github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
} else if action == model.OAUTH_ACTION_SSO_TO_EMAIL {
|
} else if action == model.OAUTH_ACTION_SSO_TO_EMAIL {
|
||||||
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
||||||
} else {
|
} else {
|
||||||
session, err := c.App.DoLogin(w, r, user, "")
|
err = c.App.DoLogin(w, r, user, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err.Translate(c.App.T)
|
err.Translate(c.App.T)
|
||||||
c.Err = err
|
c.Err = err
|
||||||
@@ -293,9 +293,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
c.App.AttachSessionCookies(w, r, session)
|
c.App.AttachSessionCookies(w, r)
|
||||||
|
|
||||||
c.App.Session = *session
|
|
||||||
|
|
||||||
if _, ok := props["redirect_to"]; ok {
|
if _, ok := props["redirect_to"]; ok {
|
||||||
redirectUrl = props["redirect_to"]
|
redirectUrl = props["redirect_to"]
|
||||||
|
|||||||
93
web/saml.go
93
web/saml.go
@@ -84,8 +84,13 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
relayProps = model.MapFromJson(strings.NewReader(stateStr))
|
relayProps = model.MapFromJson(strings.NewReader(stateStr))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.LogAudit("attempt")
|
||||||
|
|
||||||
action := relayProps["action"]
|
action := relayProps["action"]
|
||||||
if user, err := samlInterface.DoLogin(encodedXML, relayProps); err != nil {
|
user, err := samlInterface.DoLogin(encodedXML, relayProps)
|
||||||
|
if err != nil {
|
||||||
|
c.LogAudit("fail")
|
||||||
|
|
||||||
if action == model.OAUTH_ACTION_MOBILE {
|
if action == model.OAUTH_ACTION_MOBILE {
|
||||||
err.Translate(c.App.T)
|
err.Translate(c.App.T)
|
||||||
w.Write([]byte(err.ToJson()))
|
w.Write([]byte(err.ToJson()))
|
||||||
@@ -94,60 +99,62 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
c.Err.StatusCode = http.StatusFound
|
c.Err.StatusCode = http.StatusFound
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
} else {
|
}
|
||||||
if err := c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
|
|
||||||
c.Err = err
|
|
||||||
c.Err.StatusCode = http.StatusFound
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
if err = c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
|
||||||
case model.OAUTH_ACTION_SIGNUP:
|
c.Err = err
|
||||||
teamId := relayProps["team_id"]
|
c.Err.StatusCode = http.StatusFound
|
||||||
if len(teamId) > 0 {
|
return
|
||||||
c.App.Srv.Go(func() {
|
}
|
||||||
if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil {
|
|
||||||
mlog.Error(err.Error())
|
switch action {
|
||||||
} else {
|
case model.OAUTH_ACTION_SIGNUP:
|
||||||
c.App.AddDirectChannels(teamId, user)
|
teamId := relayProps["team_id"]
|
||||||
}
|
if len(teamId) > 0 {
|
||||||
})
|
|
||||||
}
|
|
||||||
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
|
||||||
if err := c.App.RevokeAllSessions(user.Id); err != nil {
|
|
||||||
c.Err = err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
c.LogAuditWithUserId(user.Id, "Revoked all sessions for user")
|
|
||||||
c.App.Srv.Go(func() {
|
c.App.Srv.Go(func() {
|
||||||
if err := c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil {
|
if err = c.App.AddUserToTeamByTeamId(teamId, user); err != nil {
|
||||||
mlog.Error(err.Error())
|
mlog.Error(err.Error())
|
||||||
|
} else {
|
||||||
|
c.App.AddDirectChannels(teamId, user)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
||||||
session, err := c.App.DoLogin(w, r, user, "")
|
if err = c.App.RevokeAllSessions(user.Id); err != nil {
|
||||||
if err != nil {
|
|
||||||
c.Err = err
|
c.Err = err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
c.LogAuditWithUserId(user.Id, "Revoked all sessions for user")
|
||||||
|
c.App.Srv.Go(func() {
|
||||||
|
if err = c.App.SendSignInChangeEmail(user.Email, strings.Title(model.USER_AUTH_SERVICE_SAML)+" SSO", user.Locale, c.App.GetSiteURL()); err != nil {
|
||||||
|
mlog.Error(err.Error())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
c.App.AttachSessionCookies(w, r, session)
|
c.LogAuditWithUserId(user.Id, "obtained user")
|
||||||
|
|
||||||
c.App.Session = *session
|
err = c.App.DoLogin(w, r, user, "")
|
||||||
|
if err != nil {
|
||||||
|
c.Err = err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
if val, ok := relayProps["redirect_to"]; ok {
|
c.LogAuditWithUserId(user.Id, "success")
|
||||||
http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
switch action {
|
c.App.AttachSessionCookies(w, r)
|
||||||
case model.OAUTH_ACTION_MOBILE:
|
|
||||||
ReturnStatusOK(w)
|
if val, ok := relayProps["redirect_to"]; ok {
|
||||||
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
|
||||||
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?extra=signin_change", http.StatusFound)
|
return
|
||||||
default:
|
}
|
||||||
http.Redirect(w, r, c.GetSiteURLHeader(), http.StatusFound)
|
|
||||||
}
|
switch action {
|
||||||
|
case model.OAUTH_ACTION_MOBILE:
|
||||||
|
ReturnStatusOK(w)
|
||||||
|
case model.OAUTH_ACTION_EMAIL_TO_SSO:
|
||||||
|
http.Redirect(w, r, c.GetSiteURLHeader()+"/login?extra=signin_change", http.StatusFound)
|
||||||
|
default:
|
||||||
|
http.Redirect(w, r, c.GetSiteURLHeader(), http.StatusFound)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user