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
|
||||
$(GO) vet $(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.
|
||||
@echo Running GOFMT
|
||||
@@ -173,17 +173,15 @@ gofmt: ## Runs gofmt against all packages.
|
||||
done
|
||||
@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://github.com/golangci/golangci-lint#binary-release
|
||||
# It is recommended to NOT use go get, but instead use a binary release pinned to a version.
|
||||
@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"; \
|
||||
@if ! [ -x "$$(command -v golangci-lintt)" ]; then \
|
||||
echo "golangci-lint is not installed. Please see https://github.com/golangci/golangci-lint#install for installation instructions."; \
|
||||
exit 1; \
|
||||
fi; \
|
||||
|
||||
@echo Running golangci-lint
|
||||
$(GOPATH)/bin/golangci-lint run
|
||||
golangci-lint run
|
||||
|
||||
megacheck: ## Run megacheck on codebasis
|
||||
env GO111MODULE=off go get -u honnef.co/go/tools/cmd/megacheck
|
||||
|
||||
@@ -9,12 +9,12 @@ import (
|
||||
_ "image/gif"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/app"
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/mattermost/mattermost-server/utils"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
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
|
||||
newEmoji, resp := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif")
|
||||
CheckNoError(t, resp)
|
||||
if newEmoji.Name != emoji.Name {
|
||||
t.Fatal("create with wrong name")
|
||||
}
|
||||
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||
|
||||
// try to create an emoji with a duplicate name
|
||||
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")
|
||||
CheckNoError(t, resp)
|
||||
if newEmoji.Name != emoji.Name {
|
||||
t.Fatal("create with wrong name")
|
||||
}
|
||||
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||
|
||||
// try to create a valid jpeg 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")
|
||||
CheckNoError(t, resp)
|
||||
if newEmoji.Name != emoji.Name {
|
||||
t.Fatal("create with wrong name")
|
||||
}
|
||||
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||
|
||||
// try to create a valid png 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")
|
||||
CheckNoError(t, resp)
|
||||
if newEmoji.Name != emoji.Name {
|
||||
t.Fatal("create with wrong name")
|
||||
}
|
||||
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||
|
||||
// try to create an emoji that's too wide
|
||||
emoji = &model.Emoji{
|
||||
@@ -103,9 +95,7 @@ func TestCreateEmoji(t *testing.T) {
|
||||
|
||||
newEmoji, resp = Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif")
|
||||
CheckNoError(t, resp)
|
||||
if newEmoji.Name != emoji.Name {
|
||||
t.Fatal("create with wrong name")
|
||||
}
|
||||
require.Equal(t, newEmoji.Name, emoji.Name, "create with wrong name")
|
||||
|
||||
// try to create an emoji that's too wide
|
||||
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")
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should fail - emoji is too wide")
|
||||
}
|
||||
require.Error(t, resp.Error, "should fail - emoji is too wide")
|
||||
|
||||
// try to create an emoji that's too tall
|
||||
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")
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should fail - emoji is too tall")
|
||||
}
|
||||
require.Error(t, resp.Error, "should fail - emoji is too tall")
|
||||
|
||||
// try to create an emoji that's too large
|
||||
emoji = &model.Emoji{
|
||||
@@ -136,9 +122,7 @@ func TestCreateEmoji(t *testing.T) {
|
||||
}
|
||||
|
||||
_, resp = Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif")
|
||||
if resp.Error == nil {
|
||||
t.Fatal("should fail - emoji is too big")
|
||||
}
|
||||
require.Error(t, resp.Error, "should fail - emoji is too big")
|
||||
|
||||
// try to create an emoji with data that isn't an image
|
||||
emoji = &model.Emoji{
|
||||
@@ -224,9 +208,7 @@ func TestGetEmojiList(t *testing.T) {
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Fatalf("failed to get emoji with id %v, %v", emoji.Id, len(listEmoji))
|
||||
}
|
||||
require.Truef(t, found, "failed to get emoji with id %v, %v", emoji.Id, len(listEmoji))
|
||||
}
|
||||
|
||||
_, resp = Client.DeleteEmoji(emojis[0].Id)
|
||||
@@ -245,16 +227,12 @@ func TestGetEmojiList(t *testing.T) {
|
||||
listEmoji, resp = Client.GetEmojiList(0, 1)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(listEmoji) != 1 {
|
||||
t.Fatal("should only return 1")
|
||||
}
|
||||
require.Len(t, listEmoji, 1, "should only return 1")
|
||||
|
||||
listEmoji, resp = Client.GetSortedEmojiList(0, 100, model.EMOJI_SORT_BY_NAME)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(listEmoji) == 0 {
|
||||
t.Fatal("should return more than 0")
|
||||
}
|
||||
require.Greater(t, len(listEmoji), 0, "should return more than 0")
|
||||
}
|
||||
|
||||
func TestDeleteEmoji(t *testing.T) {
|
||||
@@ -283,14 +261,11 @@ func TestDeleteEmoji(t *testing.T) {
|
||||
|
||||
ok, resp := Client.DeleteEmoji(newEmoji.Id)
|
||||
CheckNoError(t, resp)
|
||||
if !ok {
|
||||
t.Fatal("should return true")
|
||||
} else {
|
||||
_, err := Client.GetEmoji(newEmoji.Id)
|
||||
if err == nil {
|
||||
t.Fatal("should not return the emoji it was deleted")
|
||||
}
|
||||
}
|
||||
require.True(t, ok, "delete did not return OK")
|
||||
|
||||
_, resp = Client.GetEmoji(newEmoji.Id)
|
||||
require.NotNil(t, resp, "nil response")
|
||||
require.Error(t, resp.Error, "expected error fetching deleted emoji")
|
||||
|
||||
//Admin can delete other users emoji
|
||||
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)
|
||||
CheckNoError(t, resp)
|
||||
if !ok {
|
||||
t.Fatal("should return true")
|
||||
} else {
|
||||
_, err := th.SystemAdminClient.GetEmoji(newEmoji.Id)
|
||||
if err == nil {
|
||||
t.Fatal("should not return the emoji it was deleted")
|
||||
}
|
||||
}
|
||||
require.True(t, ok, "delete did not return OK")
|
||||
|
||||
_, resp = th.SystemAdminClient.GetEmoji(newEmoji.Id)
|
||||
require.NotNil(t, resp, "nil response")
|
||||
require.Error(t, resp.Error, "expected error fetching deleted emoji")
|
||||
|
||||
// Try to delete just deleted emoji
|
||||
_, resp = Client.DeleteEmoji(newEmoji.Id)
|
||||
@@ -445,9 +417,7 @@ func TestGetEmoji(t *testing.T) {
|
||||
|
||||
emoji, resp = Client.GetEmoji(newEmoji.Id)
|
||||
CheckNoError(t, resp)
|
||||
if emoji.Id != newEmoji.Id {
|
||||
t.Fatal("wrong emoji was returned")
|
||||
}
|
||||
require.Equal(t, newEmoji.Id, emoji.Id, "wrong emoji was returned")
|
||||
|
||||
_, resp = Client.GetEmoji(model.NewId())
|
||||
CheckNotFoundStatus(t, resp)
|
||||
@@ -506,15 +476,11 @@ func TestGetEmojiImage(t *testing.T) {
|
||||
|
||||
emojiImage, resp := Client.GetEmojiImage(emoji1.Id)
|
||||
CheckNoError(t, resp)
|
||||
if len(emojiImage) <= 0 {
|
||||
t.Fatal("should return the image")
|
||||
}
|
||||
require.Greater(t, len(emojiImage), 0, "should return the image")
|
||||
|
||||
_, imageType, err := image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
||||
} else if imageType != "gif" {
|
||||
t.Fatal("should've received gif data")
|
||||
}
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, imageType, "gif", "expected gif")
|
||||
|
||||
emoji2 := &model.Emoji{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
@@ -526,15 +492,11 @@ func TestGetEmojiImage(t *testing.T) {
|
||||
|
||||
emojiImage, resp = Client.GetEmojiImage(emoji2.Id)
|
||||
CheckNoError(t, resp)
|
||||
if len(emojiImage) <= 0 {
|
||||
t.Fatal("should return the image")
|
||||
}
|
||||
require.Greater(t, len(emojiImage), 0, "no image returned")
|
||||
|
||||
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
||||
} else if imageType != "gif" {
|
||||
t.Fatal("should've received gif data")
|
||||
}
|
||||
require.NoError(t, err, "unable to indentify received image")
|
||||
require.Equal(t, imageType, "gif", "expected gif")
|
||||
|
||||
emoji3 := &model.Emoji{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
@@ -545,15 +507,11 @@ func TestGetEmojiImage(t *testing.T) {
|
||||
|
||||
emojiImage, resp = Client.GetEmojiImage(emoji3.Id)
|
||||
CheckNoError(t, resp)
|
||||
if len(emojiImage) <= 0 {
|
||||
t.Fatal("should return the image")
|
||||
}
|
||||
require.Greater(t, len(emojiImage), 0, "no image returned")
|
||||
|
||||
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
||||
} else if imageType != "jpeg" {
|
||||
t.Fatal("should've received gif data")
|
||||
}
|
||||
require.NoError(t, err, "unable to indentify received image")
|
||||
require.Equal(t, imageType, "jpeg", "expected jpeg")
|
||||
|
||||
emoji4 := &model.Emoji{
|
||||
CreatorId: th.BasicUser.Id,
|
||||
@@ -564,15 +522,11 @@ func TestGetEmojiImage(t *testing.T) {
|
||||
|
||||
emojiImage, resp = Client.GetEmojiImage(emoji4.Id)
|
||||
CheckNoError(t, resp)
|
||||
if len(emojiImage) <= 0 {
|
||||
t.Fatal("should return the image")
|
||||
}
|
||||
require.Greater(t, len(emojiImage), 0, "no image returned")
|
||||
|
||||
_, imageType, err = image.DecodeConfig(bytes.NewReader(emojiImage))
|
||||
if err != nil {
|
||||
t.Fatalf("unable to identify received image: %v", err.Error())
|
||||
} else if imageType != "png" {
|
||||
t.Fatal("should've received gif data")
|
||||
}
|
||||
require.NoError(t, err, "unable to idenitify received image")
|
||||
require.Equal(t, imageType, "png", "expected png")
|
||||
|
||||
_, resp = Client.DeleteEmoji(emoji4.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
@@ -27,8 +27,11 @@ func (th *testHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
assert.NotEmpty(th.t, string(bb))
|
||||
poir := model.PostActionIntegrationRequestFromJson(bytes.NewReader(bb))
|
||||
assert.NotEmpty(th.t, poir.UserId)
|
||||
assert.NotEmpty(th.t, poir.UserName)
|
||||
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.TriggerId)
|
||||
assert.Equal(th.t, "button", poir.Type)
|
||||
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
@@ -15,22 +17,22 @@ func TestGetOldClientLicense(t *testing.T) {
|
||||
license, resp := Client.GetOldClientLicense("")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(license["IsLicensed"]) == 0 {
|
||||
t.Fatal("license not returned correctly")
|
||||
}
|
||||
require.NotEqual(t, license["IsLicensed"], "", "license not returned correctly")
|
||||
|
||||
Client.Logout()
|
||||
|
||||
_, resp = Client.GetOldClientLicense("")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if _, err := Client.DoApiGet("/license/client", ""); err == nil || err.StatusCode != http.StatusNotImplemented {
|
||||
t.Fatal("should have errored with 501")
|
||||
}
|
||||
_, err := Client.DoApiGet("/license/client", "")
|
||||
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 {
|
||||
t.Fatal("should have errored with 400")
|
||||
}
|
||||
_, err = Client.DoApiGet("/license/client?format=junk", "")
|
||||
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("")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
@@ -6,10 +6,11 @@ package api4
|
||||
import (
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCreateOAuthApp(t *testing.T) {
|
||||
@@ -35,21 +36,14 @@ func TestCreateOAuthApp(t *testing.T) {
|
||||
rapp, resp := AdminClient.CreateOAuthApp(oapp)
|
||||
CheckNoError(t, resp)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
if rapp.Name != oapp.Name {
|
||||
t.Fatal("names did not match")
|
||||
}
|
||||
|
||||
if rapp.IsTrusted != oapp.IsTrusted {
|
||||
t.Fatal("trusted did no match")
|
||||
}
|
||||
assert.Equal(t, oapp.Name, rapp.Name, "names did not match")
|
||||
assert.Equal(t, oapp.IsTrusted, rapp.IsTrusted, "trusted did no match")
|
||||
|
||||
// Revoke permission from regular users.
|
||||
th.RemovePermissionFromRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
_, resp = Client.CreateOAuthApp(oapp)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
// Grant permission to regular users.
|
||||
th.AddPermissionToRole(model.PERMISSION_MANAGE_OAUTH.Id, model.SYSTEM_USER_ROLE_ID)
|
||||
|
||||
@@ -57,23 +51,15 @@ func TestCreateOAuthApp(t *testing.T) {
|
||||
CheckNoError(t, resp)
|
||||
CheckCreatedStatus(t, resp)
|
||||
|
||||
if rapp.IsTrusted {
|
||||
t.Fatal("trusted should be false - created by non admin")
|
||||
}
|
||||
assert.False(t, rapp.IsTrusted, "trusted should be false - created by non admin")
|
||||
|
||||
oapp.Name = ""
|
||||
_, resp = AdminClient.CreateOAuthApp(oapp)
|
||||
CheckBadRequestStatus(t, resp)
|
||||
|
||||
if r, err := Client.DoApiPost("/oauth/apps", "garbage"); err == nil {
|
||||
t.Fatal("should have failed")
|
||||
} else {
|
||||
if r.StatusCode != http.StatusBadRequest {
|
||||
t.Log("actual: " + strconv.Itoa(r.StatusCode))
|
||||
t.Log("expected: " + strconv.Itoa(http.StatusBadRequest))
|
||||
t.Fatal("wrong status code")
|
||||
}
|
||||
}
|
||||
r, err := Client.DoApiPost("/oauth/apps", "garbage")
|
||||
require.Error(t, err, "expected error from garbage post")
|
||||
assert.Equal(t, http.StatusBadRequest, r.StatusCode)
|
||||
|
||||
Client.Logout()
|
||||
_, resp = Client.CreateOAuthApp(oapp)
|
||||
@@ -122,54 +108,22 @@ func TestUpdateOAuthApp(t *testing.T) {
|
||||
|
||||
updatedApp, resp := AdminClient.UpdateOAuthApp(oapp)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if updatedApp.Id != oapp.Id {
|
||||
t.Fatal("Id should have not updated")
|
||||
}
|
||||
|
||||
if updatedApp.CreatorId != oapp.CreatorId {
|
||||
t.Fatal("CreatorId should have not 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")
|
||||
}
|
||||
assert.Equal(t, oapp.Id, updatedApp.Id, "Id should have not updated")
|
||||
assert.Equal(t, oapp.CreatorId, updatedApp.CreatorId, "CreatorId 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")
|
||||
assert.Equal(t, oapp.Name, updatedApp.Name, "Name should have updated")
|
||||
assert.Equal(t, oapp.Description, updatedApp.Description, "Description should have updated")
|
||||
assert.Equal(t, oapp.IconURL, updatedApp.IconURL, "IconURL should have updated")
|
||||
|
||||
if len(updatedApp.CallbackUrls) == len(oapp.CallbackUrls) {
|
||||
for i, callbackUrl := range updatedApp.CallbackUrls {
|
||||
if callbackUrl != oapp.CallbackUrls[i] {
|
||||
t.Fatal("Description should have updated")
|
||||
}
|
||||
assert.Equal(t, oapp.CallbackUrls[i], callbackUrl, "Description should have updated")
|
||||
}
|
||||
}
|
||||
|
||||
if updatedApp.Homepage != oapp.Homepage {
|
||||
t.Fatal("Homepage should have updated")
|
||||
}
|
||||
|
||||
if updatedApp.IsTrusted != oapp.IsTrusted {
|
||||
t.Fatal("IsTrusted should have updated")
|
||||
}
|
||||
assert.Equal(t, oapp.Homepage, updatedApp.Homepage, "Homepage should have updated")
|
||||
assert.Equal(t, oapp.IsTrusted, updatedApp.IsTrusted, "IsTrusted should have updated")
|
||||
|
||||
th.LoginBasic2()
|
||||
updatedApp.CreatorId = th.BasicUser2.Id
|
||||
@@ -241,24 +195,16 @@ func TestGetOAuthApps(t *testing.T) {
|
||||
found2 = true
|
||||
}
|
||||
}
|
||||
|
||||
if !found1 || !found2 {
|
||||
t.Fatal("missing oauth app")
|
||||
}
|
||||
assert.Truef(t, found1, "missing oauth app %v", rapp.Id)
|
||||
assert.Truef(t, found2, "missing oauth app %v", rapp2.Id)
|
||||
|
||||
apps, resp = AdminClient.GetOAuthApps(1, 1)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(apps) != 1 {
|
||||
t.Fatal("paging failed")
|
||||
}
|
||||
require.Equal(t, 1, len(apps), "paging failed")
|
||||
|
||||
apps, resp = Client.GetOAuthApps(0, 1000)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(apps) != 1 && apps[0].Id != rapp2.Id {
|
||||
t.Fatal("wrong apps returned")
|
||||
}
|
||||
require.True(t, len(apps) == 1 || apps[0].Id == rapp2.Id, "wrong apps returned")
|
||||
|
||||
// Revoke permission from regular users.
|
||||
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)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rapp.Id != rrapp.Id {
|
||||
t.Fatal("wrong app")
|
||||
}
|
||||
|
||||
if rrapp.ClientSecret == "" {
|
||||
t.Fatal("should not be sanitized")
|
||||
}
|
||||
assert.Equal(t, rapp.Id, rrapp.Id, "wrong app")
|
||||
assert.NotEqual(t, "", rrapp.ClientSecret, "should not be sanitized")
|
||||
|
||||
rrapp2, resp := AdminClient.GetOAuthApp(rapp2.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rapp2.Id != rrapp2.Id {
|
||||
t.Fatal("wrong app")
|
||||
}
|
||||
|
||||
if rrapp2.ClientSecret == "" {
|
||||
t.Fatal("should not be sanitized")
|
||||
}
|
||||
assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app")
|
||||
assert.NotEqual(t, "", rrapp2.ClientSecret, "should not be sanitized")
|
||||
|
||||
_, resp = Client.GetOAuthApp(rapp2.Id)
|
||||
CheckNoError(t, resp)
|
||||
@@ -380,25 +314,13 @@ func TestGetOAuthAppInfo(t *testing.T) {
|
||||
|
||||
rrapp, resp := AdminClient.GetOAuthAppInfo(rapp.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rapp.Id != rrapp.Id {
|
||||
t.Fatal("wrong app")
|
||||
}
|
||||
|
||||
if rrapp.ClientSecret != "" {
|
||||
t.Fatal("should be sanitized")
|
||||
}
|
||||
assert.Equal(t, rapp.Id, rrapp.Id, "wrong app")
|
||||
assert.Equal(t, "", rrapp.ClientSecret, "should be sanitized")
|
||||
|
||||
rrapp2, resp := AdminClient.GetOAuthAppInfo(rapp2.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rapp2.Id != rrapp2.Id {
|
||||
t.Fatal("wrong app")
|
||||
}
|
||||
|
||||
if rrapp2.ClientSecret != "" {
|
||||
t.Fatal("should be sanitized")
|
||||
}
|
||||
assert.Equal(t, rapp2.Id, rrapp2.Id, "wrong app")
|
||||
assert.Equal(t, "", rrapp2.ClientSecret, "should be sanitized")
|
||||
|
||||
_, resp = Client.GetOAuthAppInfo(rapp2.Id)
|
||||
CheckNoError(t, resp)
|
||||
@@ -456,10 +378,7 @@ func TestDeleteOAuthApp(t *testing.T) {
|
||||
|
||||
pass, resp := AdminClient.DeleteOAuthApp(rapp.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if !pass {
|
||||
t.Fatal("should have passed")
|
||||
}
|
||||
assert.True(t, pass, "should have passed")
|
||||
|
||||
_, resp = AdminClient.DeleteOAuthApp(rapp2.Id)
|
||||
CheckNoError(t, resp)
|
||||
@@ -526,14 +445,8 @@ func TestRegenerateOAuthAppSecret(t *testing.T) {
|
||||
|
||||
rrapp, resp := AdminClient.RegenerateOAuthAppSecret(rapp.Id)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rrapp.Id != rapp.Id {
|
||||
t.Fatal("wrong app")
|
||||
}
|
||||
|
||||
if rrapp.ClientSecret == rapp.ClientSecret {
|
||||
t.Fatal("secret didn't change")
|
||||
}
|
||||
assert.Equal(t, rrapp.Id, rapp.Id, "wrong app")
|
||||
assert.NotEqual(t, rapp.ClientSecret, rrapp.ClientSecret, "secret didn't change")
|
||||
|
||||
_, resp = AdminClient.RegenerateOAuthAppSecret(rapp2.Id)
|
||||
CheckNoError(t, resp)
|
||||
@@ -608,15 +521,9 @@ func TestGetAuthorizedOAuthAppsForUser(t *testing.T) {
|
||||
if a.Id == rapp.Id {
|
||||
found = true
|
||||
}
|
||||
|
||||
if a.ClientSecret != "" {
|
||||
t.Fatal("not sanitized")
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("missing app")
|
||||
assert.Equal(t, "", a.ClientSecret, "not sanitized")
|
||||
}
|
||||
require.True(t, found, "missing app")
|
||||
|
||||
_, resp = Client.GetAuthorizedOAuthAppsForUser(th.BasicUser2.Id, 0, 1000)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/mattermost/mattermost-server/model"
|
||||
)
|
||||
|
||||
@@ -61,19 +61,12 @@ func TestGetOpenGraphMetadata(t *testing.T) {
|
||||
|
||||
openGraph, resp := Client.OpenGraph(ts.URL + data["path"].(string))
|
||||
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) {
|
||||
t.Fatal(fmt.Sprintf(
|
||||
"Cache miss count didn't match. Expected value %d. Actual value %d.",
|
||||
data["cacheMissCount"].(int), ogDataCacheMissCount,
|
||||
))
|
||||
}
|
||||
require.Equalf(t, openGraph["title"], data["title"].(string),
|
||||
"OG data title mismatch for path \"%s\".")
|
||||
|
||||
require.Equal(t, ogDataCacheMissCount, data["cacheMissCount"].(int),
|
||||
"Cache miss count didn't match.")
|
||||
}
|
||||
|
||||
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")
|
||||
|
||||
session, err := c.App.DoLogin(w, r, user, deviceId)
|
||||
err = c.App.DoLogin(w, r, user, deviceId)
|
||||
if err != nil {
|
||||
c.Err = err
|
||||
return
|
||||
@@ -1408,7 +1408,7 @@ func login(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.LogAuditWithUserId(user.Id, "success")
|
||||
|
||||
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)
|
||||
|
||||
@@ -33,17 +33,9 @@ func TestCreateIncomingWebhook(t *testing.T) {
|
||||
rhook, resp := th.SystemAdminClient.CreateIncomingWebhook(hook)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rhook.ChannelId != hook.ChannelId {
|
||||
t.Fatal("channel 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")
|
||||
}
|
||||
require.Equal(t, hook.ChannelId, rhook.ChannelId, "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")
|
||||
|
||||
hook.ChannelId = "junk"
|
||||
_, resp = th.SystemAdminClient.CreateIncomingWebhook(hook)
|
||||
@@ -136,16 +128,12 @@ func TestGetIncomingWebhooks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("missing hook")
|
||||
}
|
||||
require.True(t, found, "missing hook")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetIncomingWebhooks(0, 1, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(hooks) != 1 {
|
||||
t.Fatal("should only be 1")
|
||||
}
|
||||
require.Len(t, hooks, 1, "should only be 1 hook")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||
CheckNoError(t, resp)
|
||||
@@ -157,16 +145,12 @@ func TestGetIncomingWebhooks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("missing hook")
|
||||
}
|
||||
require.True(t, found, "missing hook")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetIncomingWebhooksForTeam(model.NewId(), 0, 1000, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(hooks) != 0 {
|
||||
t.Fatal("no hooks should be returned")
|
||||
}
|
||||
require.Len(t, hooks, 0, "no hooks should be returned")
|
||||
|
||||
_, resp = Client.GetIncomingWebhooks(0, 1000, "")
|
||||
CheckForbiddenStatus(t, resp)
|
||||
@@ -335,11 +319,10 @@ func TestDeleteIncomingWebhook(t *testing.T) {
|
||||
rhook, resp = Client.CreateIncomingWebhook(hook)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if status, resp = Client.DeleteIncomingWebhook(rhook.Id); !status {
|
||||
t.Fatal("Delete should have succeeded")
|
||||
} else {
|
||||
CheckOKStatus(t, resp)
|
||||
}
|
||||
status, resp = Client.DeleteIncomingWebhook(rhook.Id)
|
||||
require.True(t, status, "Delete should have succeeded")
|
||||
|
||||
CheckOKStatus(t, resp)
|
||||
|
||||
// Get now should not return this deleted hook
|
||||
_, resp = Client.GetIncomingWebhook(rhook.Id, "")
|
||||
@@ -378,13 +361,9 @@ func TestCreateOutgoingWebhook(t *testing.T) {
|
||||
rhook, resp := th.SystemAdminClient.CreateOutgoingWebhook(hook)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if rhook.ChannelId != hook.ChannelId {
|
||||
t.Fatal("channel ids didn't match")
|
||||
} else if rhook.CreatorId != th.SystemAdminUser.Id {
|
||||
t.Fatal("user ids didn't match")
|
||||
} else if rhook.TeamId != th.BasicChannel.TeamId {
|
||||
t.Fatal("team ids didn't match")
|
||||
}
|
||||
assert.Equal(t, hook.ChannelId, rhook.ChannelId, "channel ids didn't match")
|
||||
assert.Equal(t, th.SystemAdminUser.Id, rhook.CreatorId, "user ids didn't match")
|
||||
assert.Equal(t, th.BasicChannel.TeamId, rhook.TeamId, "team ids didn't match")
|
||||
|
||||
hook.ChannelId = "junk"
|
||||
_, resp = th.SystemAdminClient.CreateOutgoingWebhook(hook)
|
||||
@@ -436,16 +415,12 @@ func TestGetOutgoingWebhooks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("missing hook")
|
||||
}
|
||||
require.True(t, found, "missing hook")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooks(0, 1, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(hooks) != 1 {
|
||||
t.Fatal("should only be 1")
|
||||
}
|
||||
require.Len(t, hooks, 1, "should only be 1 hook")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(th.BasicTeam.Id, 0, 1000, "")
|
||||
CheckNoError(t, resp)
|
||||
@@ -457,16 +432,12 @@ func TestGetOutgoingWebhooks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("missing hook")
|
||||
}
|
||||
require.True(t, found, "missing hook")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForTeam(model.NewId(), 0, 1000, "")
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if len(hooks) != 0 {
|
||||
t.Fatal("no hooks should be returned")
|
||||
}
|
||||
require.Len(t, hooks, 0, "no hooks should be returned")
|
||||
|
||||
hooks, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(th.BasicChannel.Id, 0, 1000, "")
|
||||
CheckNoError(t, resp)
|
||||
@@ -478,9 +449,7 @@ func TestGetOutgoingWebhooks(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
t.Fatal("missing hook")
|
||||
}
|
||||
require.True(t, found, "missing hook")
|
||||
|
||||
_, resp = th.SystemAdminClient.GetOutgoingWebhooksForChannel(model.NewId(), 0, 1000, "")
|
||||
CheckForbiddenStatus(t, resp)
|
||||
@@ -647,9 +616,8 @@ func TestGetOutgoingWebhook(t *testing.T) {
|
||||
|
||||
getHook, resp := th.SystemAdminClient.GetOutgoingWebhook(rhook.Id)
|
||||
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)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
@@ -694,29 +662,13 @@ func TestUpdateIncomingHook(t *testing.T) {
|
||||
|
||||
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
if updatedHook != nil {
|
||||
if updatedHook.DisplayName != "hook2" {
|
||||
t.Fatal("Hook name is not updated")
|
||||
}
|
||||
|
||||
if updatedHook.Description != "description" {
|
||||
t.Fatal("Hook description is not updated")
|
||||
}
|
||||
|
||||
if updatedHook.ChannelId != th.BasicChannel2.Id {
|
||||
t.Fatal("Hook channel is not 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")
|
||||
}
|
||||
require.NotNil(t, updatedHook, "should not be nil")
|
||||
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")
|
||||
require.Empty(t, updatedHook.Username, "Hook username was incorrectly updated")
|
||||
require.Empty(t, updatedHook.IconURL, "Hook icon was incorrectly updated")
|
||||
|
||||
//updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id)
|
||||
assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId)
|
||||
@@ -734,29 +686,13 @@ func TestUpdateIncomingHook(t *testing.T) {
|
||||
|
||||
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
if updatedHook != nil {
|
||||
if updatedHook.DisplayName != "hook2" {
|
||||
t.Fatal("Hook name is not updated")
|
||||
}
|
||||
|
||||
if updatedHook.Description != "description" {
|
||||
t.Fatal("Hook description is not updated")
|
||||
}
|
||||
|
||||
if updatedHook.ChannelId != th.BasicChannel2.Id {
|
||||
t.Fatal("Hook channel 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")
|
||||
}
|
||||
require.NotNil(t, updatedHook, "should not be nil")
|
||||
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")
|
||||
require.Exactly(t, "username", updatedHook.Username, "Hook username is not updated")
|
||||
require.Exactly(t, "icon", updatedHook.IconURL, "Hook icon is not updated")
|
||||
|
||||
//updatedHook, _ = th.App.GetIncomingWebhook(createdHook.Id)
|
||||
assert.Equal(t, updatedHook.ChannelId, createdHook.ChannelId)
|
||||
@@ -781,13 +717,8 @@ func TestUpdateIncomingHook(t *testing.T) {
|
||||
|
||||
updatedHook, resp := th.SystemAdminClient.UpdateIncomingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
if updatedHook != nil {
|
||||
if updatedHook.UpdateAt == createdHook.UpdateAt {
|
||||
t.Fatal("failed - hook updateAt is not updated")
|
||||
}
|
||||
} else {
|
||||
t.Fatal("should not be nil")
|
||||
}
|
||||
require.NotNil(t, updatedHook, "should not be nil")
|
||||
require.NotEqual(t, createdHook.UpdateAt, updatedHook.UpdateAt, "failed - hook updateAt is not updated")
|
||||
})
|
||||
|
||||
t.Run("UpdateNonExistentHook", func(t *testing.T) {
|
||||
@@ -837,9 +768,7 @@ func TestUpdateIncomingHook(t *testing.T) {
|
||||
t.Run("UpdateByDifferentUser", func(t *testing.T) {
|
||||
updatedHook, resp := Client.UpdateIncomingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
if updatedHook.UserId == th.BasicUser2.Id {
|
||||
t.Fatal("Hook's creator userId is not retained")
|
||||
}
|
||||
require.NotEqual(t, th.BasicUser2.Id, updatedHook.UserId, "Hook's creator userId is not retained")
|
||||
})
|
||||
|
||||
t.Run("IncomingHooksDisabled", func(t *testing.T) {
|
||||
@@ -932,9 +861,7 @@ func TestRegenOutgoingHookToken(t *testing.T) {
|
||||
|
||||
regenHookToken, resp := th.SystemAdminClient.RegenOutgoingHookToken(rhook.Id)
|
||||
CheckNoError(t, resp)
|
||||
if regenHookToken.Token == rhook.Token {
|
||||
t.Fatal("regen didn't work properly")
|
||||
}
|
||||
require.NotEqual(t, rhook.Token, regenHookToken.Token, "regen didn't work properly")
|
||||
|
||||
_, resp = Client.RegenOutgoingHookToken(rhook.Id)
|
||||
CheckForbiddenStatus(t, resp)
|
||||
@@ -969,12 +896,9 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
||||
|
||||
updatedHook, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
if updatedHook.DisplayName != "Cats" {
|
||||
t.Fatal("did not update")
|
||||
}
|
||||
if updatedHook.Description != "Get me some 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")
|
||||
})
|
||||
|
||||
t.Run("OutgoingHooksDisabled", func(t *testing.T) {
|
||||
@@ -995,9 +919,7 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
||||
updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook2)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if updatedHook2.CreateAt != createdHook2.CreateAt {
|
||||
t.Fatal("failed - hook create at should not be changed")
|
||||
}
|
||||
require.Equal(t, createdHook2.CreateAt, updatedHook2.CreateAt, "failed - hook create at should not be changed")
|
||||
})
|
||||
|
||||
t.Run("ModifyUpdateAt", func(t *testing.T) {
|
||||
@@ -1006,9 +928,7 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
||||
updatedHook2, resp := th.SystemAdminClient.UpdateOutgoingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if updatedHook2.UpdateAt == createdHook.UpdateAt {
|
||||
t.Fatal("failed - hook updateAt is not updated")
|
||||
}
|
||||
require.NotEqual(t, createdHook.UpdateAt, updatedHook2.UpdateAt, "failed - hook updateAt is not updated")
|
||||
})
|
||||
|
||||
t.Run("UpdateNonExistentHook", func(t *testing.T) {
|
||||
@@ -1048,12 +968,9 @@ func TestUpdateOutgoingHook(t *testing.T) {
|
||||
createdHook.DisplayName = "Basic user 2"
|
||||
updatedHook, resp := Client.UpdateOutgoingWebhook(createdHook)
|
||||
CheckNoError(t, resp)
|
||||
if updatedHook.DisplayName != "Basic user 2" {
|
||||
t.Fatal("should apply the change")
|
||||
}
|
||||
if updatedHook.CreatorId != th.SystemAdminUser.Id {
|
||||
t.Fatal("hook creator should not be changed")
|
||||
}
|
||||
|
||||
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")
|
||||
})
|
||||
|
||||
t.Run("UpdateToExistingTriggerWordAndCallback", func(t *testing.T) {
|
||||
@@ -1167,11 +1084,10 @@ func TestDeleteOutgoingHook(t *testing.T) {
|
||||
rhook, resp = Client.CreateOutgoingWebhook(hook)
|
||||
CheckNoError(t, resp)
|
||||
|
||||
if status, resp = Client.DeleteOutgoingWebhook(rhook.Id); !status {
|
||||
t.Fatal("Delete should have succeeded")
|
||||
} else {
|
||||
CheckOKStatus(t, resp)
|
||||
}
|
||||
status, resp = Client.DeleteOutgoingWebhook(rhook.Id)
|
||||
|
||||
require.True(t, status, "Delete should have succeeded")
|
||||
CheckOKStatus(t, resp)
|
||||
|
||||
// Get now should not return this deleted hook
|
||||
_, resp = Client.GetIncomingWebhook(rhook.Id, "")
|
||||
|
||||
@@ -45,6 +45,20 @@ func TestCreateBot(t *testing.T) {
|
||||
require.NotNil(t, err)
|
||||
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) {
|
||||
|
||||
@@ -143,6 +143,10 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str
|
||||
var text string
|
||||
if err.Id == "api.channel.add_members.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 {
|
||||
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",
|
||||
expected: "api.command_invite.fail.app_error",
|
||||
expected: "api.command_invite.user_not_in_team.app_error",
|
||||
msg: basicUser4.Username,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -186,6 +186,7 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "@channel", post.Message)
|
||||
assert.Equal(t, "true", post.Props["from_webhook"])
|
||||
|
||||
// Test Slack attachments text conversion.
|
||||
resp.Attachments = []*model.SlackAttachment{
|
||||
@@ -196,7 +197,11 @@ func TestHandleCommandResponsePost(t *testing.T) {
|
||||
|
||||
post, err = th.App.HandleCommandResponsePost(command, args, resp, builtIn)
|
||||
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)
|
||||
resp.ChannelId = channel.Id
|
||||
|
||||
@@ -334,7 +334,7 @@ func (a *App) ImportUser(data *UserImportData, dryRun bool) *model.AppError {
|
||||
authData = nil
|
||||
} else {
|
||||
// If no AuthData or Password is specified, we must generate a password.
|
||||
password = model.NewId()
|
||||
password = model.GeneratePassword(*a.Config().PasswordSettings.MinimumLength)
|
||||
authData = nil
|
||||
}
|
||||
|
||||
|
||||
@@ -76,6 +76,13 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
||||
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
|
||||
if result.Err != 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)
|
||||
}
|
||||
|
||||
channel, err := a.Srv.Store.Channel().Get(cookie.ChannelId, true)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
upstreamRequest.ChannelId = cookie.ChannelId
|
||||
upstreamRequest.ChannelName = channel.Name
|
||||
upstreamRequest.TeamId = channel.TeamId
|
||||
upstreamRequest.Type = cookie.Type
|
||||
upstreamRequest.Context = cookie.Integration.Context
|
||||
datasource = cookie.DataSource
|
||||
@@ -112,6 +126,7 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
||||
}
|
||||
|
||||
upstreamRequest.ChannelId = post.ChannelId
|
||||
upstreamRequest.ChannelName = channel.Name
|
||||
upstreamRequest.TeamId = channel.TeamId
|
||||
upstreamRequest.Type = action.Type
|
||||
upstreamRequest.Context = action.Integration.Context
|
||||
@@ -140,6 +155,27 @@ func (a *App) DoPostActionWithCookie(postId, actionId, userId, selectedOption st
|
||||
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 selectedOption != "" {
|
||||
if upstreamRequest.Context == nil {
|
||||
|
||||
@@ -80,8 +80,11 @@ func TestPostAction(t *testing.T) {
|
||||
assert.NotNil(t, request)
|
||||
|
||||
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.ChannelName, th.BasicChannel.Name)
|
||||
assert.Equal(t, request.TeamId, th.BasicTeam.Id)
|
||||
assert.Equal(t, request.TeamName, th.BasicTeam.Name)
|
||||
assert.True(t, len(request.TriggerId) > 0)
|
||||
if request.Type == model.POST_ACTION_TYPE_SELECT {
|
||||
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)
|
||||
}
|
||||
|
||||
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 {
|
||||
var rejectionReason string
|
||||
pluginContext := a.PluginContext()
|
||||
@@ -120,7 +120,7 @@ func (a *App) DoLogin(w http.ResponseWriter, r *http.Request, user *model.User,
|
||||
}, plugin.UserWillLogInId)
|
||||
|
||||
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
|
||||
if err := a.RevokeSessionsForDeviceId(user.Id, deviceId, ""); err != nil {
|
||||
err.StatusCode = http.StatusInternalServerError
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
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
|
||||
if session, err = a.CreateSession(session); err != nil {
|
||||
err.StatusCode = http.StatusInternalServerError
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
|
||||
w.Header().Set(model.HEADER_TOKEN, session.Token)
|
||||
|
||||
a.Session = *session
|
||||
|
||||
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
|
||||
if GetProtocol(r) == "https" {
|
||||
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)
|
||||
sessionCookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_TOKEN,
|
||||
Value: session.Token,
|
||||
Value: a.Session.Token,
|
||||
Path: subpath,
|
||||
MaxAge: maxAge,
|
||||
Expires: expiresAt,
|
||||
@@ -201,7 +202,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
||||
|
||||
userCookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_USER,
|
||||
Value: session.UserId,
|
||||
Value: a.Session.UserId,
|
||||
Path: subpath,
|
||||
MaxAge: maxAge,
|
||||
Expires: expiresAt,
|
||||
@@ -211,7 +212,7 @@ func (a *App) AttachSessionCookies(w http.ResponseWriter, r *http.Request, sessi
|
||||
|
||||
csrfCookie := &http.Cookie{
|
||||
Name: model.SESSION_COOKIE_CSRF,
|
||||
Value: session.GetCSRF(),
|
||||
Value: a.Session.GetCSRF(),
|
||||
Path: subpath,
|
||||
MaxAge: maxAge,
|
||||
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) {
|
||||
th := Setup(t).InitBasic()
|
||||
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{}
|
||||
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)
|
||||
}
|
||||
@@ -733,10 +733,10 @@ func TestUserWillLogInIn_Passed(t *testing.T) {
|
||||
|
||||
r := &http.Request{}
|
||||
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.Equal(t, session.UserId, th.BasicUser.Id)
|
||||
assert.Equal(t, th.App.Session.UserId, th.BasicUser.Id)
|
||||
}
|
||||
|
||||
func TestUserHasLoggedIn(t *testing.T) {
|
||||
@@ -774,7 +774,7 @@ func TestUserHasLoggedIn(t *testing.T) {
|
||||
|
||||
r := &http.Request{}
|
||||
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)
|
||||
|
||||
|
||||
@@ -52,7 +52,3 @@ services:
|
||||
http.host: "0.0.0.0"
|
||||
transport.host: "127.0.0.1"
|
||||
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
|
||||
redis:
|
||||
image: redis
|
||||
networks:
|
||||
- mm-test
|
||||
|
||||
@@ -28,10 +28,6 @@ services:
|
||||
extends:
|
||||
file: docker-compose.common.yml
|
||||
service: elasticsearch
|
||||
redis:
|
||||
extends:
|
||||
file: docker-compose.common.yml
|
||||
service: redis
|
||||
|
||||
start_dependencies:
|
||||
image: mattermost/mattermost-wait-for-dep:latest
|
||||
@@ -44,8 +40,7 @@ services:
|
||||
- inbucket
|
||||
- openldap
|
||||
- elasticsearch
|
||||
- redis
|
||||
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 redis:6379
|
||||
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200
|
||||
|
||||
networks:
|
||||
mm-test:
|
||||
|
||||
@@ -55,9 +55,3 @@ clean-old-docker:
|
||||
docker stop mattermost-elasticsearch > /dev/null; \
|
||||
docker rm -v mattermost-elasticsearch > /dev/null; \
|
||||
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:
|
||||
file: build/docker-compose.common.yml
|
||||
service: elasticsearch
|
||||
redis:
|
||||
container_name: mattermost-redis
|
||||
ports:
|
||||
- "6379:6379"
|
||||
extends:
|
||||
file: build/docker-compose.common.yml
|
||||
service: redis
|
||||
start_dependencies:
|
||||
image: mattermost/mattermost-wait-for-dep:latest
|
||||
networks:
|
||||
@@ -64,8 +57,7 @@ services:
|
||||
- inbucket
|
||||
- openldap
|
||||
- elasticsearch
|
||||
- redis
|
||||
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200 redis:6379
|
||||
command: postgres:5432 mysql:3306 minio:9000 inbucket:10080 openldap:389 elasticsearch:9200
|
||||
|
||||
networks:
|
||||
mm-test:
|
||||
|
||||
1
go.mod
1
go.mod
@@ -16,7 +16,6 @@ require (
|
||||
github.com/fortytw2/leaktest v1.3.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.4.7
|
||||
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/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
|
||||
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-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-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.1 h1:g24URVg0OFbNUTx9qqY1IRZ9D9z3iPyi5zKhQZpNwpA=
|
||||
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",
|
||||
"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",
|
||||
"translation": "You don't have permission to invite new users to this server."
|
||||
|
||||
@@ -86,11 +86,13 @@ func (schedulers *Schedulers) Start() *Schedulers {
|
||||
}
|
||||
|
||||
for {
|
||||
timer := time.NewTimer(1 * time.Minute)
|
||||
select {
|
||||
case <-schedulers.stop:
|
||||
mlog.Debug("Schedulers received stop signal.")
|
||||
timer.Stop()
|
||||
return
|
||||
case now = <-time.After(1 * time.Minute):
|
||||
case now = <-timer.C:
|
||||
cfg := schedulers.jobs.Config()
|
||||
|
||||
for idx, nextTime := range schedulers.nextRunTimes {
|
||||
@@ -128,6 +130,7 @@ func (schedulers *Schedulers) Start() *Schedulers {
|
||||
}
|
||||
}
|
||||
}
|
||||
timer.Stop()
|
||||
}
|
||||
})
|
||||
}()
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
@@ -167,7 +166,7 @@ func UserFromBot(b *Bot) *User {
|
||||
return &User{
|
||||
Id: b.UserId,
|
||||
Username: b.Username,
|
||||
Email: fmt.Sprintf("%s@localhost", strings.ToLower(b.Username)),
|
||||
Email: NormalizeEmail(fmt.Sprintf("%s@localhost", b.Username)),
|
||||
FirstName: b.DisplayName,
|
||||
Roles: SYSTEM_USER_ROLE_ID,
|
||||
}
|
||||
|
||||
@@ -157,14 +157,17 @@ type PostActionIntegration struct {
|
||||
}
|
||||
|
||||
type PostActionIntegrationRequest struct {
|
||||
UserId string `json:"user_id"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
TeamId string `json:"team_id"`
|
||||
PostId string `json:"post_id"`
|
||||
TriggerId string `json:"trigger_id"`
|
||||
Type string `json:"type"`
|
||||
DataSource string `json:"data_source"`
|
||||
Context map[string]interface{} `json:"context,omitempty"`
|
||||
UserId string `json:"user_id"`
|
||||
UserName string `json:"user_name"`
|
||||
ChannelId string `json:"channel_id"`
|
||||
ChannelName string `json:"channel_name"`
|
||||
TeamId string `json:"team_id"`
|
||||
TeamName string `json:"team_domain"`
|
||||
PostId string `json:"post_id"`
|
||||
TriggerId string `json:"trigger_id"`
|
||||
Type string `json:"type"`
|
||||
DataSource string `json:"data_source"`
|
||||
Context map[string]interface{} `json:"context,omitempty"`
|
||||
}
|
||||
|
||||
type PostActionIntegrationResponse struct {
|
||||
|
||||
@@ -9,10 +9,12 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/mattermost/mattermost-server/services/timezones"
|
||||
@@ -851,3 +853,27 @@ func UsersWithGroupsAndCountFromJson(data io.Reader) *UsersWithGroupsAndCount {
|
||||
json.Unmarshal(bodyBytes, 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 (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -350,3 +351,25 @@ func TestUserSlice(t *testing.T) {
|
||||
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.
|
||||
// Appropriate context such as the plugin name will already be added as fields so plugins
|
||||
// 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
|
||||
LogDebug(msg string, keyValuePairs ...interface{})
|
||||
@@ -671,7 +670,6 @@ type API interface {
|
||||
// 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
|
||||
// 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
|
||||
LogInfo(msg string, keyValuePairs ...interface{})
|
||||
@@ -679,7 +677,6 @@ type API interface {
|
||||
// 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
|
||||
// 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
|
||||
LogError(msg string, keyValuePairs ...interface{})
|
||||
@@ -687,7 +684,6 @@ type API interface {
|
||||
// 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
|
||||
// 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
|
||||
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"
|
||||
)
|
||||
|
||||
func TestRunCheck(t *testing.T) {
|
||||
func TestCheckAPIVersionComments(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name, pkgPath, err string
|
||||
expected result
|
||||
}{
|
||||
{
|
||||
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: "",
|
||||
},
|
||||
{
|
||||
name: "invalid comments",
|
||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/invalid",
|
||||
err: "test/invalid/invalid.go:15:2: missing a minimum server version comment\n",
|
||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/internal/test/invalid",
|
||||
expected: result{
|
||||
Errors: []string{"internal/test/invalid/invalid.go:15:2: missing a minimum server version comment on method InvalidMethod"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "missing API interface",
|
||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/missing",
|
||||
err: "could not find API interface in package 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",
|
||||
},
|
||||
{
|
||||
name: "non-existent package path",
|
||||
pkgPath: "github.com/mattermost/mattermost-server/plugin/checker/test/does_not_exist",
|
||||
err: "could not find API interface in package 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",
|
||||
},
|
||||
}
|
||||
|
||||
@@ -43,7 +46,8 @@ func TestRunCheck(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
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 != "" {
|
||||
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.
|
||||
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
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go/ast"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
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() {
|
||||
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, err)
|
||||
fmt.Fprintln(os.Stderr, strings.Join(msgs, "\n"))
|
||||
}
|
||||
|
||||
if len(res.Errors) > 0 {
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func runCheck(pkgPath string) error {
|
||||
pkg, err := getPackage(pkgPath)
|
||||
func runCheck(prev result, fn checkFn) result {
|
||||
res, err := fn(pluginPackagePath)
|
||||
if err != nil {
|
||||
return err
|
||||
prev.Errors = append(prev.Errors, err.Error())
|
||||
return prev
|
||||
}
|
||||
|
||||
apiInterface := findAPIInterface(pkg.Syntax)
|
||||
if apiInterface == nil {
|
||||
return errors.Errorf("could not find API interface in package %s", pkgPath)
|
||||
if len(res.Warnings) > 0 {
|
||||
prev.Warnings = append(prev.Warnings, mapWarnings(res.Warnings)...)
|
||||
}
|
||||
|
||||
invalidMethods := findInvalidMethods(apiInterface.Methods.List)
|
||||
if len(invalidMethods) > 0 {
|
||||
return errors.New(renderErrorMessage(pkg, invalidMethods))
|
||||
if len(res.Errors) > 0 {
|
||||
prev.Errors = append(prev.Errors, res.Errors...)
|
||||
}
|
||||
return nil
|
||||
|
||||
return prev
|
||||
}
|
||||
|
||||
func getPackage(pkgPath string) (*packages.Package, error) {
|
||||
cfg := &packages.Config{
|
||||
Mode: packages.NeedName | packages.NeedTypes | packages.NeedSyntax,
|
||||
func mapWarnings(ss []string) []string {
|
||||
var out []string
|
||||
for _, s := range ss {
|
||||
out = append(out, "[warn] "+s)
|
||||
}
|
||||
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 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()
|
||||
return out
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
"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/model"
|
||||
)
|
||||
@@ -531,3 +531,118 @@ func (s *hooksRPCServer) MessageWillBeUpdated(args *Z_MessageWillBeUpdatedArgs,
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
A string
|
||||
B string
|
||||
|
||||
@@ -8,9 +8,13 @@ import "github.com/mattermost/mattermost-server/model"
|
||||
type Helpers interface {
|
||||
// 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.
|
||||
//
|
||||
// Minimum server version: 5.10
|
||||
EnsureBot(bot *model.Bot) (string, error)
|
||||
|
||||
// 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
|
||||
|
||||
// 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)
|
||||
|
||||
// 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)
|
||||
|
||||
// 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",
|
||||
"MessageWillBePosted",
|
||||
"MessageWillBeUpdated",
|
||||
"LogDebug",
|
||||
"LogInfo",
|
||||
"LogWarn",
|
||||
"LogError",
|
||||
}
|
||||
for _, exclusion := range excluded {
|
||||
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"
|
||||
|
||||
"github.com/mattermost/mattermost-server/einterfaces"
|
||||
"github.com/mattermost/mattermost-server/mlog"
|
||||
)
|
||||
|
||||
const (
|
||||
ENABLE_EXPERIMENTAL_REDIS = false
|
||||
)
|
||||
|
||||
type LayeredStoreDatabaseLayer interface {
|
||||
@@ -23,7 +18,6 @@ type LayeredStore struct {
|
||||
TmpContext context.Context
|
||||
DatabaseLayer LayeredStoreDatabaseLayer
|
||||
LocalCacheLayer *LocalCacheSupplier
|
||||
RedisLayer *RedisSupplier
|
||||
LayerChainHead LayeredStoreSupplier
|
||||
}
|
||||
|
||||
@@ -35,15 +29,8 @@ func NewLayeredStore(db LayeredStoreDatabaseLayer, metrics einterfaces.MetricsIn
|
||||
}
|
||||
|
||||
// Setup the chain
|
||||
if ENABLE_EXPERIMENTAL_REDIS {
|
||||
mlog.Debug("Experimental redis enabled.")
|
||||
store.RedisLayer = NewRedisSupplier()
|
||||
store.RedisLayer.SetChainNext(store.DatabaseLayer)
|
||||
store.LayerChainHead = store.RedisLayer
|
||||
} else {
|
||||
store.LocalCacheLayer.SetChainNext(store.DatabaseLayer)
|
||||
store.LayerChainHead = store.LocalCacheLayer
|
||||
}
|
||||
store.LocalCacheLayer.SetChainNext(store.DatabaseLayer)
|
||||
store.LayerChainHead = store.LocalCacheLayer
|
||||
|
||||
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)
|
||||
require.Nil(t, err)
|
||||
|
||||
if _, err = ss.Session().Get(s1.Token); err == nil {
|
||||
t.Fatal("should error - session should be deleted")
|
||||
}
|
||||
_, err = ss.Session().Get(s1.Token)
|
||||
require.NotNil(t, err, "should error - session should be deleted")
|
||||
|
||||
_, err = ss.OAuth().GetAccessData(s1.Token)
|
||||
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/fsnotify/fsnotify v1.4.7
|
||||
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
|
||||
# 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 {
|
||||
redirectUrl = app.GetProtocol(r) + "://" + r.Host + "/claim?email=" + url.QueryEscape(props["email"])
|
||||
} else {
|
||||
session, err := c.App.DoLogin(w, r, user, "")
|
||||
err = c.App.DoLogin(w, r, user, "")
|
||||
if err != nil {
|
||||
err.Translate(c.App.T)
|
||||
c.Err = err
|
||||
@@ -293,9 +293,7 @@ func completeOAuth(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
c.App.AttachSessionCookies(w, r, session)
|
||||
|
||||
c.App.Session = *session
|
||||
c.App.AttachSessionCookies(w, r)
|
||||
|
||||
if _, ok := props["redirect_to"]; ok {
|
||||
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))
|
||||
}
|
||||
|
||||
c.LogAudit("attempt")
|
||||
|
||||
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 {
|
||||
err.Translate(c.App.T)
|
||||
w.Write([]byte(err.ToJson()))
|
||||
@@ -94,60 +99,62 @@ func completeSaml(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.Err.StatusCode = http.StatusFound
|
||||
}
|
||||
return
|
||||
} else {
|
||||
if err := c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusFound
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch action {
|
||||
case model.OAUTH_ACTION_SIGNUP:
|
||||
teamId := relayProps["team_id"]
|
||||
if len(teamId) > 0 {
|
||||
c.App.Srv.Go(func() {
|
||||
if err := c.App.AddUserToTeamByTeamId(teamId, user); err != nil {
|
||||
mlog.Error(err.Error())
|
||||
} else {
|
||||
c.App.AddDirectChannels(teamId, user)
|
||||
}
|
||||
})
|
||||
}
|
||||
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")
|
||||
if err = c.App.CheckUserAllAuthenticationCriteria(user, ""); err != nil {
|
||||
c.Err = err
|
||||
c.Err.StatusCode = http.StatusFound
|
||||
return
|
||||
}
|
||||
|
||||
switch action {
|
||||
case model.OAUTH_ACTION_SIGNUP:
|
||||
teamId := relayProps["team_id"]
|
||||
if len(teamId) > 0 {
|
||||
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())
|
||||
} else {
|
||||
c.App.AddDirectChannels(teamId, user)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
session, err := c.App.DoLogin(w, r, user, "")
|
||||
if err != nil {
|
||||
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() {
|
||||
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 {
|
||||
http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
|
||||
return
|
||||
}
|
||||
c.LogAuditWithUserId(user.Id, "success")
|
||||
|
||||
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)
|
||||
}
|
||||
c.App.AttachSessionCookies(w, r)
|
||||
|
||||
if val, ok := relayProps["redirect_to"]; ok {
|
||||
http.Redirect(w, r, c.GetSiteURLHeader()+val, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
||||
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