Merge branch 'master' into mark-as-unread

Этот коммит содержится в:
Harrison Healey
2019-09-20 10:11:31 -04:00
родитель e6f67c664c 32bc585446
Коммит 05c2a134ac
15 изменённых файлов: 928 добавлений и 547 удалений

Просмотреть файл

@@ -1,8 +1,224 @@
version: 2.1 version: 2.1
orbs:
aws-s3: circleci/aws-s3@1.0.11
executors:
ubuntu:
working_directory: ~/go/src/github.com/mattermost/
machine:
image: "ubuntu-1604:201903-01"
environment:
COMPOSE_PROJECT_NAME: "circleci"
jobs: jobs:
setup:
working_directory: /go/src/github.com/mattermost/mattermost-server
docker:
- image: mattermost/mattermost-build-webapp:oct-2-2018
steps:
- checkout
- run: |
cd ../
mkdir -p ~/.ssh/
echo -e "Host github.com\n\tStrictHostKeyChecking no\n" > ~/.ssh/config
git clone git@github.com:mattermost/mattermost-webapp.git
cd mattermost-webapp
git checkout $CIRCLE_BRANCH || git checkout master
export WEBAPP_GIT_COMMIT=$(git rev-parse HEAD)
echo "$WEBAPP_GIT_COMMIT"
curl -f -o ./dist.tar.gz https://releases.mattermost.com/mattermost-webapp/commit/${WEBAPP_GIT_COMMIT}/mattermost-webapp.tar.gz && mkdir ./dist && tar -xvf ./dist.tar.gz -C ./dist --strip-components=1 || make node_modules test build
- persist_to_workspace:
root: /go/src/github.com/mattermost
paths:
- mattermost-webapp
- mattermost-server
# TODO: enable this step when the i18n-extract works with mattermost-server only
# and not depend on both mm-server/enterprise
# check-i18n:
# docker:
# - image: circleci/golang:1.12
# working_directory: /go/src/github.com/mattermost/
# steps:
# - attach_workspace:
# at: /go/src/github.com/mattermost/
# - run:
# command: |
# cd mattermost-server
# cp i18n/en.json /tmp/en.json
# make i18n-extract
# diff /tmp/en.json i18n/en.json
build: build:
docker: docker:
- image: circleci/golang:1.12 - image: mattermost/mattermost-build-server:feb-28-2019
working_directory: /go/src/github.com/mattermost
steps: steps:
- run: echo "skipping build. PR \#11978 in progress." - attach_workspace:
at: /go/src/github.com/mattermost/
- run:
command: |
cd mattermost-server
make config-reset
make check-style BUILD_NUMBER='${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}'
GOFLAGS=-p=8 make build BUILD_NUMBER='${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}'
GOFLAGS=-p=8 make package BUILD_NUMBER='${CIRCLE_BRANCH}-${CIRCLE_BUILD_NUM}'
- store_artifacts:
path: /go/src/github.com/mattermost/mattermost-server/dist/mattermost-team-linux-amd64.tar.gz
- store_artifacts:
path: /go/src/github.com/mattermost/mattermost-server/dist/mattermost-team-osx-amd64.tar.gz
- store_artifacts:
path: /go/src/github.com/mattermost/mattermost-server/dist/mattermost-team-windows-amd64.zip
- persist_to_workspace:
root: /go/src/github.com/mattermost
paths:
- mattermost-server
- mattermost-webapp
test:
executor:
name: ubuntu
steps:
- attach_workspace:
at: ~/go/src/github.com/mattermost
- run:
name: Run Docker compose
command: |
cd mattermost-server/build
docker-compose --no-ansi run --rm start_dependencies
cat ../tests/test-data.ldif | docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
docker-compose --no-ansi exec -T minio sh -c 'mkdir -p /data/mattermost-test';
docker-compose --no-ansi ps
background: true
- run:
name: Wait for docker compose
command: |
sleep 5
docker run --net circleci_mm-test appropriate/curl:latest sh -c "until curl --max-time 5 --output - http://mysql:3306; do echo waiting for mysql; sleep 5; done;"
docker run --net circleci_mm-test appropriate/curl:latest sh -c "until curl --max-time 5 --output - http://elasticsearch:9200; do echo waiting for elasticsearch; sleep 5; done;"
- run:
name: Run Tests
command: |
ulimit -n 8096
mkdir -p mattermost-server/client/plugins
docker run -it --net circleci_mm-test \
--env TEST_DATABASE_MYSQL_DSN="mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s" \
--env TEST_DATABASE_POSTGRESQL_DSN="postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10" \
--env TEST_DATABASE_MYSQL_ROOT_PASSWD=mostest \
--env CI_INBUCKET_HOST=inbucket \
--env CI_MINIO_HOST=minio \
--env CI_INBUCKET_PORT=10080 \
--env CI_MINIO_PORT=9000 \
--env CI_INBUCKET_SMTP_PORT=10025 \
--env CI_LDAP_HOST=openldap \
--env IS_CI="true" \
--env MM_SQLSETTINGS_DATASOURCE="mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8" \
--env MM_EMAILSETTINGS_SMTPSERVER=inbucket \
--env MM_EMAILSETTINGS_SMTPPORT=10025 \
--env MM_ELASTICSEARCHSETTINGS_CONNECTIONURL=http://elasticsearch:9200 \
-v ~/go/src:/go/src \
-w /go/src/github.com/mattermost/mattermost-server \
mattermost/mattermost-build-server:feb-28-2019 \
bash -c 'ulimit -n 8096; make test-server BUILD_NUMBER="$CIRCLE_BRANCH-$CIRCLE_PREVIOUS_BUILD_NUM" TESTFLAGS= TESTFLAGSEE='
no_output_timeout: 1h
- run:
name: Capture docker logs
when: always
command: |
cd mattermost-server/build
# Capture docker logs
mkdir -p logs
docker-compose logs --tail="all" -t --no-color > logs/docker-compose_logs
docker ps -a --no-trunc > logs/docker_ps
docker stats -a --no-stream > logs/docker_stats
tar -czvf logs/docker_logs.tar.gz logs/docker-compose_logs logs/docker_ps logs/docker_stats
- store_artifacts:
path: ~/go/src/github.com/mattermost/mattermost-server/build/logs
- run:
when: always
command: |
cd mattermost-server
mkdir -p test-results
cp report.xml test-results
- store_test_results:
path: ~/go/src/github.com/mattermost/mattermost-server/test-results/
- store_artifacts:
path: ~/go/src/github.com/mattermost/mattermost-server/test-results/
upload-s3-sha:
docker:
- image: 'circleci/python:2.7'
working_directory: ~/go/src/github.com/mattermost/enterprise
steps:
- attach_workspace:
at: ~/go/src/github.com/mattermost/
- run:
command: |
cd ~/go/src/github.com/mattermost/mattermost-server/dist/
rm -rf mattermost
- aws-s3/copy:
from: ~/go/src/github.com/mattermost/mattermost-server/dist/
to: "s3://releases.mattermost.com/mattermost-platform-pr/commit/${CIRCLE_SHA1}/"
arguments: --acl public-read --cache-control "no-cache" --recursive
upload-s3:
docker:
- image: 'circleci/python:2.7'
working_directory: ~/go/src/github.com/mattermost/enterprise
steps:
- attach_workspace:
at: ~/go/src/github.com/mattermost/
- run:
command: |
cd ~/go/src/github.com/mattermost/mattermost-server/dist/
rm -rf mattermost
- aws-s3/copy:
from: ~/go/src/github.com/mattermost/mattermost-server/dist/
to: s3://releases.mattermost.com/mattermost-platform-pr/$(echo "${CIRCLE_BRANCH}" | sed 's/pull\//PR-/g')/
arguments: --acl public-read --cache-control "no-cache" --recursive
build-docker:
working_directory: ~/
docker:
- image: circleci/buildpack-deps:stretch
steps:
- attach_workspace:
at: .
- setup_remote_docker
- run:
command: |
export TAG="${CIRCLE_SHA1:0:7}"
cd mattermost-server
export MM_PACKAGE=https://releases.mattermost.com/mattermost-platform-pr/commit/${CIRCLE_SHA1}/mattermost-team-linux-amd64.tar.gz
docker build --build-arg MM_PACKAGE=$MM_PACKAGE -t mattermost/mattermost-team-edition:${TAG} build
echo $DOCKER_PASSWORD | docker login --username $DOCKER_USERNAME --password-stdin
docker push mattermost/mattermost-team-edition:${TAG}
workflows:
version: 2
untagged-build:
jobs:
- setup
# - check-i18n:
# requires:
# - setup
- test:
requires:
- setup
- build:
requires:
- test
- upload-s3-sha:
context: mattermost-ci-s3
requires:
- build
- upload-s3:
context: mattermost-ci-s3
requires:
- build
- build-docker:
context: matterbuild-docker
requires:
- upload-s3-sha

Просмотреть файл

@@ -18,6 +18,7 @@ func (api *API) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request))
handler := &web.Handler{ handler := &web.Handler{
GetGlobalAppOptions: api.GetGlobalAppOptions, GetGlobalAppOptions: api.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: web.GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: false, TrustRequester: false,
RequireMfa: false, RequireMfa: false,
@@ -35,6 +36,7 @@ func (api *API) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.R
handler := &web.Handler{ handler := &web.Handler{
GetGlobalAppOptions: api.GetGlobalAppOptions, GetGlobalAppOptions: api.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: web.GetHandlerName(h),
RequireSession: true, RequireSession: true,
TrustRequester: false, TrustRequester: false,
RequireMfa: true, RequireMfa: true,
@@ -54,6 +56,7 @@ func (api *API) ApiSessionRequiredMfa(h func(*Context, http.ResponseWriter, *htt
handler := &web.Handler{ handler := &web.Handler{
GetGlobalAppOptions: api.GetGlobalAppOptions, GetGlobalAppOptions: api.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: web.GetHandlerName(h),
RequireSession: true, RequireSession: true,
TrustRequester: false, TrustRequester: false,
RequireMfa: false, RequireMfa: false,
@@ -73,6 +76,7 @@ func (api *API) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *
handler := &web.Handler{ handler := &web.Handler{
GetGlobalAppOptions: api.GetGlobalAppOptions, GetGlobalAppOptions: api.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: web.GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: true, TrustRequester: true,
RequireMfa: false, RequireMfa: false,
@@ -91,6 +95,7 @@ func (api *API) ApiSessionRequiredTrustRequester(h func(*Context, http.ResponseW
handler := &web.Handler{ handler := &web.Handler{
GetGlobalAppOptions: api.GetGlobalAppOptions, GetGlobalAppOptions: api.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: web.GetHandlerName(h),
RequireSession: true, RequireSession: true,
TrustRequester: true, TrustRequester: true,
RequireMfa: true, RequireMfa: true,

Просмотреть файл

@@ -806,6 +806,13 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
var autocomplete model.UserAutocomplete var autocomplete model.UserAutocomplete
var err *model.AppError
options, err = c.App.RestrictUsersSearchByPermissions(c.App.Session.UserId, options)
if err != nil {
c.Err = err
return
}
if len(channelId) > 0 { if len(channelId) > 0 {
// Applying the provided teamId here is useful for DMs and GMs which don't belong // Applying the provided teamId here is useful for DMs and GMs which don't belong
// to a team. Applying it when the channel does belong to a team makes less sense, // to a team. Applying it when the channel does belong to a team makes less sense,
@@ -819,13 +826,6 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
autocomplete.Users = result.InChannel autocomplete.Users = result.InChannel
autocomplete.OutOfChannel = result.OutOfChannel autocomplete.OutOfChannel = result.OutOfChannel
} else if len(teamId) > 0 { } else if len(teamId) > 0 {
var err *model.AppError
options, err = c.App.RestrictUsersSearchByPermissions(c.App.Session.UserId, options)
if err != nil {
c.Err = err
return
}
result, err := c.App.AutocompleteUsersInTeam(teamId, name, options) result, err := c.App.AutocompleteUsersInTeam(teamId, name, options)
if err != nil { if err != nil {
c.Err = err c.Err = err
@@ -834,13 +834,6 @@ func autocompleteUsers(c *Context, w http.ResponseWriter, r *http.Request) {
autocomplete.Users = result.InTeam autocomplete.Users = result.InTeam
} else { } else {
var err *model.AppError
options, err = c.App.RestrictUsersSearchByPermissions(c.App.Session.UserId, options)
if err != nil {
c.Err = err
return
}
result, err := c.App.SearchUsersInTeam("", name, options) result, err := c.App.SearchUsersInTeam("", name, options)
if err != nil { if err != nil {
c.Err = err c.Err = err

Просмотреть файл

@@ -997,133 +997,262 @@ func findUserInList(id string, users []*model.User) bool {
return false return false
} }
func TestAutocompleteUsers(t *testing.T) { func TestAutocompleteUsersInChannel(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()
teamId := th.BasicTeam.Id teamId := th.BasicTeam.Id
channelId := th.BasicChannel.Id channelId := th.BasicChannel.Id
username := th.BasicUser.Username username := th.BasicUser.Username
newUser := th.CreateUser()
rusers, resp := th.Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "") tt := []struct {
CheckNoError(t, resp) Name string
TeamId string
if len(rusers.Users) != 1 { ChannelId string
t.Fatal("should have returned 1 user") Username string
ExpectedResults int
MoreThan bool
}{
{
"Autocomplete in channel for specific username",
teamId,
channelId,
username,
1,
false,
},
{
"Search for not valid username",
teamId,
channelId,
"amazonses",
0,
false,
},
{
"Search for all users",
teamId,
channelId,
"",
2,
true,
},
{
"Search all in specific channel",
"",
channelId,
"",
2,
true,
},
} }
rusers, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, "amazonses", model.USER_SEARCH_DEFAULT_LIMIT, "") for _, tc := range tt {
CheckNoError(t, resp) t.Run(tc.Name, func(t *testing.T) {
if len(rusers.Users) != 0 { th.LoginBasic()
t.Fatal("should have returned 0 users") rusers, resp := th.Client.AutocompleteUsersInChannel(tc.TeamId, tc.ChannelId, tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if tc.MoreThan {
assert.True(t, len(rusers.Users) >= tc.ExpectedResults)
} else {
assert.Len(t, rusers.Users, tc.ExpectedResults)
}
th.Client.Logout()
_, resp = th.Client.AutocompleteUsersInChannel(tc.TeamId, tc.ChannelId, tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
th.Client.Login(newUser.Email, newUser.Password)
_, resp = th.Client.AutocompleteUsersInChannel(tc.TeamId, tc.ChannelId, tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckForbiddenStatus(t, resp)
})
} }
rusers, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "") t.Run("Check against privacy config settings", func(t *testing.T) {
CheckNoError(t, resp) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PrivacySettings.ShowFullName = false })
if len(rusers.Users) < 2 {
t.Fatal("should have many users")
}
rusers, resp = th.Client.AutocompleteUsersInChannel("", channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "") th.LoginBasic()
CheckNoError(t, resp) rusers, resp := th.Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
if len(rusers.Users) < 2 { CheckNoError(t, resp)
t.Fatal("should have many users")
}
rusers, resp = th.Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "") assert.Equal(t, rusers.Users[0].FirstName, "", "should not show first/last name")
CheckNoError(t, resp) assert.Equal(t, rusers.Users[0].LastName, "", "should not show first/last name")
})
if len(rusers.Users) != 1 { t.Run("Check OutOfChannel results with/without VIEW_MEMBERS permissions", func(t *testing.T) {
t.Fatal("should have returned 1 user") permissionsUser := th.CreateUser()
} th.SystemAdminClient.DemoteUserToGuest(permissionsUser.Id)
permissionsUser.Roles = "system_guest"
th.LinkUserToTeam(permissionsUser, th.BasicTeam)
th.AddUserToChannel(permissionsUser, th.BasicChannel)
rusers, resp = th.Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "") otherUser := th.CreateUser()
CheckNoError(t, resp) th.LinkUserToTeam(otherUser, th.BasicTeam)
if len(rusers.Users) != 1 { th.Client.Login(permissionsUser.Email, permissionsUser.Password)
t.Fatal("should have returned 1 users")
}
rusers, resp = th.Client.AutocompleteUsers("", model.USER_SEARCH_DEFAULT_LIMIT, "") rusers, resp := th.Client.AutocompleteUsersInChannel(teamId, channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp) CheckNoError(t, resp)
assert.Len(t, rusers.OutOfChannel, 1)
if len(rusers.Users) < 2 { defaultRolePermissions := th.SaveDefaultRolePermissions()
t.Fatal("should have returned many users") defer func() {
} th.RestoreDefaultRolePermissions(defaultRolePermissions)
}()
rusers, resp = th.Client.AutocompleteUsersInTeam(teamId, "amazonses", model.USER_SEARCH_DEFAULT_LIMIT, "") th.RemovePermissionFromRole(model.PERMISSION_VIEW_MEMBERS.Id, model.SYSTEM_USER_ROLE_ID)
CheckNoError(t, resp) th.RemovePermissionFromRole(model.PERMISSION_VIEW_MEMBERS.Id, model.TEAM_USER_ROLE_ID)
if len(rusers.Users) != 0 {
t.Fatal("should have returned 0 users")
}
rusers, resp = th.Client.AutocompleteUsersInTeam(teamId, "", model.USER_SEARCH_DEFAULT_LIMIT, "") rusers, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp) CheckNoError(t, resp)
if len(rusers.Users) < 2 { assert.Len(t, rusers.OutOfChannel, 0)
t.Fatal("should have many users")
}
th.Client.Logout() th.App.GetOrCreateDirectChannel(permissionsUser.Id, otherUser.Id)
_, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
_, resp = th.Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "") rusers, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, "", model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp) CheckNoError(t, resp)
assert.Len(t, rusers.OutOfChannel, 1)
_, resp = th.Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "") })
CheckUnauthorizedStatus(t, resp)
user := th.CreateUser()
th.Client.Login(user.Email, user.Password)
_, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckForbiddenStatus(t, resp)
_, resp = th.Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckForbiddenStatus(t, resp)
_, resp = th.Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
_, resp = th.SystemAdminClient.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
_, resp = th.SystemAdminClient.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
_, resp = th.SystemAdminClient.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
// Check against privacy config settings
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PrivacySettings.ShowFullName = false })
th.LoginBasic()
rusers, resp = th.Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
t.Fatal("should not show first/last name")
}
rusers, resp = th.Client.AutocompleteUsersInChannel(teamId, channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
t.Fatal("should not show first/last name")
}
rusers, resp = th.Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if rusers.Users[0].FirstName != "" || rusers.Users[0].LastName != "" {
t.Fatal("should not show first/last name")
}
t.Run("user must have access to team id, especially when it does not match channel's team id", func(t *testing.T) { t.Run("user must have access to team id, especially when it does not match channel's team id", func(t *testing.T) {
rusers, resp = th.Client.AutocompleteUsersInChannel("otherTeamId", channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "") _, resp := th.Client.AutocompleteUsersInChannel("otherTeamId", channelId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckErrorMessage(t, resp, "api.context.permissions.app_error") CheckErrorMessage(t, resp, "api.context.permissions.app_error")
}) })
} }
func TestAutocompleteUsersInTeam(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
teamId := th.BasicTeam.Id
username := th.BasicUser.Username
newUser := th.CreateUser()
tt := []struct {
Name string
TeamId string
Username string
ExpectedResults int
MoreThan bool
}{
{
"specific username",
teamId,
username,
1,
false,
},
{
"not valid username",
teamId,
"amazonses",
0,
false,
},
{
"all users in team",
teamId,
"",
2,
true,
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
th.LoginBasic()
rusers, resp := th.Client.AutocompleteUsersInTeam(tc.TeamId, tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if tc.MoreThan {
assert.True(t, len(rusers.Users) >= tc.ExpectedResults)
} else {
assert.Len(t, rusers.Users, tc.ExpectedResults)
}
th.Client.Logout()
_, resp = th.Client.AutocompleteUsersInTeam(tc.TeamId, tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
th.Client.Login(newUser.Email, newUser.Password)
_, resp = th.Client.AutocompleteUsersInTeam(tc.TeamId, tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckForbiddenStatus(t, resp)
})
}
t.Run("Check against privacy config settings", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PrivacySettings.ShowFullName = false })
th.LoginBasic()
rusers, resp := th.Client.AutocompleteUsersInTeam(teamId, username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
assert.Equal(t, rusers.Users[0].FirstName, "", "should not show first/last name")
assert.Equal(t, rusers.Users[0].LastName, "", "should not show first/last name")
})
}
func TestAutocompleteUsers(t *testing.T) {
th := Setup().InitBasic()
defer th.TearDown()
username := th.BasicUser.Username
newUser := th.CreateUser()
tt := []struct {
Name string
Username string
ExpectedResults int
MoreThan bool
}{
{
"specific username",
username,
1,
false,
},
{
"not valid username",
"amazonses",
0,
false,
},
{
"all users in team",
"",
2,
true,
},
}
for _, tc := range tt {
t.Run(tc.Name, func(t *testing.T) {
th.LoginBasic()
rusers, resp := th.Client.AutocompleteUsers(tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
if tc.MoreThan {
assert.True(t, len(rusers.Users) >= tc.ExpectedResults)
} else {
assert.Len(t, rusers.Users, tc.ExpectedResults)
}
th.Client.Logout()
_, resp = th.Client.AutocompleteUsers(tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckUnauthorizedStatus(t, resp)
th.Client.Login(newUser.Email, newUser.Password)
_, resp = th.Client.AutocompleteUsers(tc.Username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
})
}
t.Run("Check against privacy config settings", func(t *testing.T) {
th.App.UpdateConfig(func(cfg *model.Config) { *cfg.PrivacySettings.ShowFullName = false })
th.LoginBasic()
rusers, resp := th.Client.AutocompleteUsers(username, model.USER_SEARCH_DEFAULT_LIMIT, "")
CheckNoError(t, resp)
assert.Equal(t, rusers.Users[0].FirstName, "", "should not show first/last name")
assert.Equal(t, rusers.Users[0].LastName, "", "should not show first/last name")
})
}
func TestGetProfileImage(t *testing.T) { func TestGetProfileImage(t *testing.T) {
th := Setup().InitBasic() th := Setup().InitBasic()
defer th.TearDown() defer th.TearDown()

Просмотреть файл

@@ -4,7 +4,6 @@
package app package app
import ( import (
"fmt"
"net/http" "net/http"
"strings" "strings"
@@ -192,8 +191,7 @@ func (a *App) RolesGrantPermission(roleNames []string, permissionId string) bool
if err != nil { if err != nil {
// This should only happen if something is very broken. We can't realistically // This should only happen if something is very broken. We can't realistically
// recover the situation, so deny permission and log an error. // recover the situation, so deny permission and log an error.
mlog.Error("Failed to get roles from database with role names: " + strings.Join(roleNames, ",")) mlog.Error("Failed to get roles from database with role names: "+strings.Join(roleNames, ",")+" ", mlog.Err(err))
mlog.Error(fmt.Sprint(err))
return false return false
} }

Просмотреть файл

@@ -23,13 +23,13 @@ func TestMuteCommandNoChannel(t *testing.T) {
channel1 := th.BasicChannel channel1 := th.BasicChannel
channel1M, channel1MError := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id) channel1M, channel1MError := th.App.GetChannelMember(channel1.Id, th.BasicUser.Id)
if channel1MError != nil { assert.Nil(t, channel1MError, "User is not a member of channel 1")
t.Fatal("User is not a member of channel 1") assert.NotEqual(
} t,
channel1M.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP],
if channel1M.NotifyProps[model.MARK_UNREAD_NOTIFY_PROP] == model.CHANNEL_NOTIFY_MENTION { model.CHANNEL_NOTIFY_MENTION,
t.Fatal("channel shouldn't be muted on initial setup") "Channel shouldn't be muted on initial setup",
} )
cmd := &MuteProvider{} cmd := &MuteProvider{}
resp := cmd.DoCommand(th.App, &model.CommandArgs{ resp := cmd.DoCommand(th.App, &model.CommandArgs{

Просмотреть файл

@@ -340,13 +340,11 @@ func TestPluginAPISavePluginConfig(t *testing.T) {
pluginConfigJsonString := `{"mystringsetting": "str", "MyIntSetting": 32, "myboolsetting": true}` pluginConfigJsonString := `{"mystringsetting": "str", "MyIntSetting": 32, "myboolsetting": true}`
var pluginConfig map[string]interface{} var pluginConfig map[string]interface{}
if err := json.Unmarshal([]byte(pluginConfigJsonString), &pluginConfig); err != nil { err := json.Unmarshal([]byte(pluginConfigJsonString), &pluginConfig)
t.Fatal(err) require.NoError(t, err)
}
if err := api.SavePluginConfig(pluginConfig); err != nil { appErr := api.SavePluginConfig(pluginConfig)
t.Fatal(err) require.Nil(t, appErr)
}
type Configuration struct { type Configuration struct {
MyStringSetting string MyStringSetting string
@@ -355,14 +353,12 @@ func TestPluginAPISavePluginConfig(t *testing.T) {
} }
savedConfiguration := new(Configuration) savedConfiguration := new(Configuration)
if err := api.LoadPluginConfiguration(savedConfiguration); err != nil { err = api.LoadPluginConfiguration(savedConfiguration)
t.Fatal(err) require.NoError(t, err)
}
expectedConfiguration := new(Configuration) expectedConfiguration := new(Configuration)
if err := json.Unmarshal([]byte(pluginConfigJsonString), &expectedConfiguration); err != nil { err = json.Unmarshal([]byte(pluginConfigJsonString), &expectedConfiguration)
t.Fatal(err) require.NoError(t, err)
}
assert.Equal(t, expectedConfiguration, savedConfiguration) assert.Equal(t, expectedConfiguration, savedConfiguration)
} }
@@ -387,9 +383,9 @@ func TestPluginAPIGetPluginConfig(t *testing.T) {
pluginConfigJsonString := `{"mystringsetting": "str", "myintsetting": 32, "myboolsetting": true}` pluginConfigJsonString := `{"mystringsetting": "str", "myintsetting": 32, "myboolsetting": true}`
var pluginConfig map[string]interface{} var pluginConfig map[string]interface{}
if err := json.Unmarshal([]byte(pluginConfigJsonString), &pluginConfig); err != nil { err := json.Unmarshal([]byte(pluginConfigJsonString), &pluginConfig)
t.Fatal(err) require.NoError(t, err)
}
th.App.UpdateConfig(func(cfg *model.Config) { th.App.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.Plugins["pluginid"] = pluginConfig cfg.PluginSettings.Plugins["pluginid"] = pluginConfig
}) })
@@ -403,9 +399,9 @@ func TestPluginAPILoadPluginConfiguration(t *testing.T) {
defer th.TearDown() defer th.TearDown()
var pluginJson map[string]interface{} var pluginJson map[string]interface{}
if err := json.Unmarshal([]byte(`{"mystringsetting": "str", "MyIntSetting": 32, "myboolsetting": true}`), &pluginJson); err != nil { err := json.Unmarshal([]byte(`{"mystringsetting": "str", "MyIntSetting": 32, "myboolsetting": true}`), &pluginJson)
t.Fatal(err) require.NoError(t, err)
}
th.App.UpdateConfig(func(cfg *model.Config) { th.App.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.Plugins["testloadpluginconfig"] = pluginJson cfg.PluginSettings.Plugins["testloadpluginconfig"] = pluginJson
}) })
@@ -474,9 +470,9 @@ func TestPluginAPILoadPluginConfigurationDefaults(t *testing.T) {
defer th.TearDown() defer th.TearDown()
var pluginJson map[string]interface{} var pluginJson map[string]interface{}
if err := json.Unmarshal([]byte(`{"mystringsetting": "override"}`), &pluginJson); err != nil { err := json.Unmarshal([]byte(`{"mystringsetting": "override"}`), &pluginJson)
t.Fatal(err) require.NoError(t, err)
}
th.App.UpdateConfig(func(cfg *model.Config) { th.App.UpdateConfig(func(cfg *model.Config) {
cfg.PluginSettings.Plugins["testloadpluginconfig"] = pluginJson cfg.PluginSettings.Plugins["testloadpluginconfig"] = pluginJson
}) })

Просмотреть файл

@@ -7,413 +7,413 @@ def utils = new org.mattermost.Utils()
def rnd = UUID.randomUUID().toString() def rnd = UUID.randomUUID().toString()
pipeline { pipeline {
agent { agent {
label 'default-mm-builder' label 'default-mm-builder'
} }
options { options {
buildDiscarder(logRotator(numToKeepStr: '3')) buildDiscarder(logRotator(numToKeepStr: '3'))
timeout(time: 1, unit: 'HOURS') timeout(time: 1, unit: 'HOURS')
} }
environment { environment {
COMPOSE_PROJECT_NAME="${rnd}-${env.BUILD_NUMBER}" COMPOSE_PROJECT_NAME="${rnd}-${env.BUILD_NUMBER}"
} }
stages { stages {
stage('Setup') { stage('Setup') {
steps { steps {
script { script {
utils.stopOldBuilds() utils.stopOldBuilds()
} }
cleanWs notFailBuild: true cleanWs notFailBuild: true
sh """ sh """
mkdir -p src/github.com/mattermost/mattermost-server mkdir -p src/github.com/mattermost/mattermost-server
mkdir -p src/github.com/mattermost/mattermost-webapp mkdir -p src/github.com/mattermost/mattermost-webapp
mkdir -p src/github.com/mattermost/enterprise mkdir -p src/github.com/mattermost/enterprise
""" """
dir('src/github.com/mattermost/mattermost-server') { dir('src/github.com/mattermost/mattermost-server') {
checkout scm checkout scm
} }
dir('src/github.com/mattermost/mattermost-webapp') { dir('src/github.com/mattermost/mattermost-webapp') {
checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '310159d3-f7c5-4f5d-bfa1-151e3ef2db57', url: 'https://github.com/mattermost/mattermost-webapp.git']]] checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '310159d3-f7c5-4f5d-bfa1-151e3ef2db57', url: 'https://github.com/mattermost/mattermost-webapp.git']]]
} }
dir('src/github.com/mattermost/enterprise') { dir('src/github.com/mattermost/enterprise') {
checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '310159d3-f7c5-4f5d-bfa1-151e3ef2db57', url: 'https://github.com/mattermost/enterprise.git']]] checkout changelog: false, poll: false, scm: [$class: 'GitSCM', branches: [[name: '*/master']], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '310159d3-f7c5-4f5d-bfa1-151e3ef2db57', url: 'https://github.com/mattermost/enterprise.git']]]
} }
dir('src/github.com/mattermost/enterprise') { dir('src/github.com/mattermost/enterprise') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
#!/bin/bash -ex #!/bin/bash -ex
git checkout $env.BRANCH_NAME || git checkout $env.CHANGE_BRANCH || git checkout $env.GIT_BRANCH || git checkout $env.CHANGE_TARGET || git checkout master || echo 1 git checkout $env.BRANCH_NAME || git checkout $env.CHANGE_BRANCH || git checkout $env.GIT_BRANCH || git checkout $env.CHANGE_TARGET || git checkout master || echo 1
export EE_GIT_COMMIT=\$(git rev-parse HEAD) export EE_GIT_COMMIT=\$(git rev-parse HEAD)
echo EE Commit: \${EE_GIT_COMMIT} echo EE Commit: \${EE_GIT_COMMIT}
""" """
} }
} }
dir('src/github.com/mattermost/mattermost-webapp') { dir('src/github.com/mattermost/mattermost-webapp') {
withDockerContainer(args: '', image: 'mattermost/mattermost-build-webapp:oct-2-2018') { withDockerContainer(args: '', image: 'mattermost/mattermost-build-webapp:oct-2-2018') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
#!/bin/bash -ex #!/bin/bash -ex
git checkout $env.BRANCH_NAME || git checkout $env.CHANGE_BRANCH || git checkout $env.GIT_BRANCH || git checkout $env.CHANGE_TARGET || git checkout master git checkout $env.BRANCH_NAME || git checkout $env.CHANGE_BRANCH || git checkout $env.GIT_BRANCH || git checkout $env.CHANGE_TARGET || git checkout master
rm -rf ./dist rm -rf ./dist
export WEBAPP_GIT_COMMIT=\$(git rev-parse HEAD) export WEBAPP_GIT_COMMIT=\$(git rev-parse HEAD)
echo Webapp Commit: \${WEBAPP_GIT_COMMIT} echo Webapp Commit: \${WEBAPP_GIT_COMMIT}
curl -f -o ./dist.tar.gz https://releases.mattermost.com/mattermost-webapp/commit/\${WEBAPP_GIT_COMMIT}/mattermost-webapp.tar.gz && mkdir ./dist && tar -xvf ./dist.tar.gz -C ./dist --strip-components=1 || make node_modules test build curl -f -o ./dist.tar.gz https://releases.mattermost.com/mattermost-webapp/commit/\${WEBAPP_GIT_COMMIT}/mattermost-webapp.tar.gz && mkdir ./dist && tar -xvf ./dist.tar.gz -C ./dist --strip-components=1 || make node_modules test build
""" """
} }
} }
} }
} }
} }
stage('Check i18n') { stage('Check i18n') {
environment { environment {
GOPATH = "/go" GOPATH = "/go"
} }
steps { steps {
withDockerContainer(args: '-u root --privileged -v ${WORKSPACE}/src:/go/src/', image: 'mattermost/mattermost-build-server:feb-28-2019') { withDockerContainer(args: '-u root --privileged -v ${WORKSPACE}/src:/go/src/', image: 'mattermost/mattermost-build-server:feb-28-2019') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
cd /go/src/github.com/mattermost/mattermost-server cd /go/src/github.com/mattermost/mattermost-server
cp i18n/en.json /tmp/en.json cp i18n/en.json /tmp/en.json
make i18n-extract make i18n-extract
diff /tmp/en.json i18n/en.json diff /tmp/en.json i18n/en.json
""" """
} }
} }
} }
} }
stage('Build') { stage('Build') {
environment { environment {
GOPATH = "/go" GOPATH = "/go"
} }
steps { steps {
withDockerContainer(args: '-u root --privileged -v ${WORKSPACE}/src:/go/src/', image: 'mattermost/mattermost-build-server:feb-28-2019') { withDockerContainer(args: '-u root --privileged -v ${WORKSPACE}/src:/go/src/', image: 'mattermost/mattermost-build-server:feb-28-2019') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
cd /go/src/github.com/mattermost/mattermost-server cd /go/src/github.com/mattermost/mattermost-server
make config-reset make config-reset
make check-style BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' make check-style BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}'
make build BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' make build BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}'
make package BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' make package BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}'
""" """
} }
} }
} }
} }
stage('Test') { stage('Push to S3') {
environment { stages {
GOPATH = "/go" stage('Pull request') {
TEST_DATABASE_MYSQL_DSN = "mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s" when {
TEST_DATABASE_POSTGRESQL_DSN = "postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10" allOf {
TEST_DATABASE_MYSQL_ROOT_PASSWD = "mostest" expression { env.CHANGE_ID != null }
CI_INBUCKET_HOST = "inbucket" expression { env.CHANGE_TARGET != null }
CI_MINIO_HOST = "minio" }
CI_INBUCKET_PORT = "10080" }
CI_MINIO_PORT = "9000" steps {
CI_INBUCKET_SMTP_PORT = "10025" dir('src/github.com/mattermost/mattermost-server/dist') {
CI_LDAP_HOST = "openldap" step([$class: 'S3BucketPublisher', dontWaitForConcurrentBuildCompletion: false, entries: [[
IS_CI = true bucket: "releases.mattermost.com/mattermost-platform-pr/${CHANGE_ID}",
MM_SQLSETTINGS_DATASOURCE = "mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8" excludedFile: '',
MM_EMAILSETTINGS_SMTPSERVER = "inbucket" flatten: true,
MM_EMAILSETTINGS_SMTPPORT = "10025" gzipFiles: false,
MM_ELASTICSEARCHSETTINGS_CONNECTIONURL = "http://elasticsearch:9200" keepForever: false,
LDAP_DATA = "test" managedArtifacts: false,
} noUploadOnFailure: true,
selectedRegion: 'us-east-1',
showDirectlyInBrowser: false,
sourceFile: '*.tar.gz',
storageClass: 'STANDARD',
uploadFromSlave: false,
useServerSideEncryption: false,
userMetadata: [[key: 'Cache-Control', value: 'no-cache']]
], [
bucket: "releases.mattermost.com/mattermost-platform-pr/${CHANGE_BRANCH}",
excludedFile: '',
flatten: true,
gzipFiles: false,
keepForever: false,
managedArtifacts: false,
noUploadOnFailure: true,
selectedRegion: 'us-east-1',
showDirectlyInBrowser: false,
sourceFile: '*.tar.gz',
storageClass: 'STANDARD',
uploadFromSlave: false,
useServerSideEncryption: false,
userMetadata: [[key: 'Cache-Control', value: 'no-cache']]
]], profileName: 'Releases', userMetadAta: []])
}
}
}
stage('Branch') {
when {
expression { env.CHANGE_ID == null }
}
steps {
dir('src/github.com/mattermost/mattermost-server/dist') {
step([$class: 'S3BucketPublisher', dontWaitForConcurrentBuildCompletion: false, entries: [[
bucket: "releases.mattermost.com/mattermost-platform-pr/${BRANCH_NAME}",
excludedFile: '',
flatten: true,
gzipFiles: false,
keepForever: false,
managedArtifacts: false,
noUploadOnFailure: true,
selectedRegion: 'us-east-1',
showDirectlyInBrowser: false,
sourceFile: '*.tar.gz',
storageClass: 'STANDARD',
uploadFromSlave: false,
useServerSideEncryption: false,
userMetadata: [[key: 'Cache-Control', value: 'no-cache']]
]], profileName: 'Releases', userMetadAta: []])
}
}
}
}
}
steps { stage('Clean checkout') {
dir('src/github.com/mattermost/mattermost-server/build') { when {
ansiColor('xterm') { expression { env.CHANGE_ID != null }
sh """ }
/usr/local/bin/docker-compose --no-ansi run --rm start_dependencies // We need to perform a clean checkout here to ge the original git commit hash from the PR
/usr/local/bin/docker-compose --no-ansi ps // Jenkins now merges master in top of the PR and this generate a new git hash
cat ../tests/${LDAP_DATA}-data.ldif | /usr/local/bin/docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest'; // We need to do that to build the docker image based on the original git commit and then this will be used by
""" // mattermod to update the test server.
} steps {
} sh """
mkdir -p /tmp/mattermost-server
"""
dir('/tmp/mattermost-server') {
checkout([$class: 'GitSCM', branches: [[name: 'FETCH_HEAD']],
doGenerateSubmoduleConfigurations: false, extensions: [],
submoduleCfg: [], userRemoteConfigs: [
[refspec: "+refs/pull/${CHANGE_ID}/head:refs/remotes/origin/PR-${CHANGE_ID}",
credentialsId: "310159d3-f7c5-4f5d-bfa1-151e3ef2db57",url: "https://github.com/mattermost/mattermost-server.git"]]])
sh 'git rev-parse --short HEAD'
}
}
}
dir('src/github.com/mattermost/mattermost-server') { stage('Build Docker Image') {
ansiColor('xterm') { environment {
sh """ GIT_COMMIT_SHORT = sh(
echo "Creating databases" script: "cd /tmp/mattermost-server && printf \$(git rev-parse --short HEAD)",
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres sh -c 'exec echo "CREATE DATABASE migrated; CREATE DATABASE latest;" | exec psql -U mmuser mattermost_test' returnStdout: true
echo "Importing postgres dump from version 5.0" )
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres psql -U mmuser -d migrated < \$(pwd)/scripts/mattermost-postgresql-5.0.sql }
""" when {
} expression { env.CHANGE_ID != null }
} }
steps {
dir('src/github.com/mattermost/mattermost-server') {
withCredentials([usernamePassword(credentialsId: 'matterbuild-docker-hub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh 'docker login --username ${DOCKER_USER} --password ${DOCKER_PASS}'
sh """
docker build --no-cache --build-arg MM_PACKAGE=https://releases.mattermost.com/mattermost-platform-pr/${CHANGE_ID}/mattermost-enterprise-linux-amd64.tar.gz -t mattermost/mattermost-enterprise-edition:${GIT_COMMIT_SHORT} build
docker push mattermost/mattermost-enterprise-edition:${GIT_COMMIT_SHORT}
docker logout
"""
}
}
}
}
withDockerContainer(args: "-u root --privileged --net ${COMPOSE_PROJECT_NAME}_mm-test -v ${WORKSPACE}/src:/go/src/", image: 'mattermost/mattermost-build-server:feb-28-2019') { stage('Test') {
ansiColor('xterm') { environment {
sh """ GOPATH = "/go"
cd /go/src/github.com/mattermost/mattermost-server TEST_DATABASE_MYSQL_DSN = "mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8\u0026readTimeout=30s\u0026writeTimeout=30s"
echo "Setting up config for db migration" TEST_DATABASE_POSTGRESQL_DSN = "postgres://mmuser:mostest@postgres:5432/mattermost_test?sslmode=disable&connect_timeout=10"
export MM_SQLSETTINGS_DATASOURCE=\"postgres://mmuser:mostest@postgres:5432/migrated?sslmode=disable&connect_timeout=10\" TEST_DATABASE_MYSQL_ROOT_PASSWD = "mostest"
export MM_SQLSETTINGS_DRIVERNAME=\"postgres\" CI_INBUCKET_HOST = "inbucket"
make ARGS="config get SqlSettings.DataSource" run-cli CI_MINIO_HOST = "minio"
echo "Running the migration" CI_INBUCKET_PORT = "10080"
make ARGS="version" run-cli CI_MINIO_PORT = "9000"
CI_INBUCKET_SMTP_PORT = "10025"
CI_LDAP_HOST = "openldap"
IS_CI = true
MM_SQLSETTINGS_DATASOURCE = "mmuser:mostest@tcp(mysql:3306)/mattermost_test?charset=utf8mb4,utf8"
MM_EMAILSETTINGS_SMTPSERVER = "inbucket"
MM_EMAILSETTINGS_SMTPPORT = "10025"
MM_ELASTICSEARCHSETTINGS_CONNECTIONURL = "http://elasticsearch:9200"
LDAP_DATA = "test"
}
echo "Setting up config for fresh db setup" steps {
export MM_SQLSETTINGS_DATASOURCE=\"postgres://mmuser:mostest@postgres:5432/latest?sslmode=disable&connect_timeout=10\" dir('src/github.com/mattermost/mattermost-server/build') {
make ARGS="config get SqlSettings.DataSource" run-cli ansiColor('xterm') {
sh """
/usr/local/bin/docker-compose --no-ansi run --rm start_dependencies
/usr/local/bin/docker-compose --no-ansi ps
cat ../tests/${LDAP_DATA}-data.ldif | /usr/local/bin/docker-compose --no-ansi exec -T openldap bash -c 'ldapadd -x -D "cn=admin,dc=mm,dc=test,dc=com" -w mostest';
"""
}
}
echo "Setting up fresh db" dir('src/github.com/mattermost/mattermost-server') {
make ARGS="version" run-cli ansiColor('xterm') {
""" sh """
} echo "Creating databases"
} /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres sh -c 'exec echo "CREATE DATABASE migrated; CREATE DATABASE latest;" | exec psql -U mmuser mattermost_test'
echo "Importing postgres dump from version 5.0"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres psql -U mmuser -d migrated < \$(pwd)/scripts/mattermost-postgresql-5.0.sql
"""
}
}
withDockerContainer(args: "-u root --privileged --net ${COMPOSE_PROJECT_NAME}_mm-test -v ${WORKSPACE}/src:/go/src/", image: 'mattermost/mattermost-build-server:feb-28-2019') {
ansiColor('xterm') {
sh """
cd /go/src/github.com/mattermost/mattermost-server
echo "Setting up config for db migration"
export MM_SQLSETTINGS_DATASOURCE=\"postgres://mmuser:mostest@postgres:5432/migrated?sslmode=disable&connect_timeout=10\"
export MM_SQLSETTINGS_DRIVERNAME=\"postgres\"
make ARGS="config get SqlSettings.DataSource" run-cli
echo "Running the migration"
make ARGS="version" run-cli
echo "Setting up config for fresh db setup"
export MM_SQLSETTINGS_DATASOURCE=\"postgres://mmuser:mostest@postgres:5432/latest?sslmode=disable&connect_timeout=10\"
make ARGS="config get SqlSettings.DataSource" run-cli
echo "Setting up fresh db"
make ARGS="version" run-cli
"""
}
}
dir('src/github.com/mattermost/mattermost-server') { dir('src/github.com/mattermost/mattermost-server') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
echo "Generating dump" echo "Generating dump"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres pg_dump --schema-only -d migrated -U mmuser > migrated.sql /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres pg_dump --schema-only -d migrated -U mmuser > migrated.sql
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres pg_dump --schema-only -d latest -U mmuser > latest.sql /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres pg_dump --schema-only -d latest -U mmuser > latest.sql
echo "Removing databases created for db comparison" echo "Removing databases created for db comparison"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres sh -c \'exec echo \"DROP DATABASE migrated; DROP DATABASE latest;\" | exec psql -U mmuser mattermost_test\' /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T postgres sh -c \'exec echo \"DROP DATABASE migrated; DROP DATABASE latest;\" | exec psql -U mmuser mattermost_test\'
echo "Generating diff" echo "Generating diff"
diff migrated.sql latest.sql > diff.txt diff migrated.sql latest.sql > diff.txt
export diffErrorCode=\$? export diffErrorCode=\$?
if [ \$diffErrorCode -eq 0 ]; then echo \"Both schemas are same\";else cat diff.txt; fi if [ \$diffErrorCode -eq 0 ]; then echo \"Both schemas are same\";else cat diff.txt; fi
exit \$diffErrorCode exit \$diffErrorCode
""" """
} }
} }
dir('src/github.com/mattermost/mattermost-server') { dir('src/github.com/mattermost/mattermost-server') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
echo "Creating databases" echo "Creating databases"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysql -uroot -pmostest -e \"CREATE DATABASE migrated; CREATE DATABASE latest; GRANT ALL PRIVILEGES ON migrated.* TO mmuser; GRANT ALL PRIVILEGES ON latest.* TO mmuser\" /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysql -uroot -pmostest -e \"CREATE DATABASE migrated; CREATE DATABASE latest; GRANT ALL PRIVILEGES ON migrated.* TO mmuser; GRANT ALL PRIVILEGES ON latest.* TO mmuser\"
echo "Importing mysql dump from version 5.0" echo "Importing mysql dump from version 5.0"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysql -D migrated -uroot -pmostest < \$(pwd)/scripts/mattermost-mysql-5.0.sql /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysql -D migrated -uroot -pmostest < \$(pwd)/scripts/mattermost-mysql-5.0.sql
""" """
} }
} }
withDockerContainer(args: "-u root --privileged --net ${COMPOSE_PROJECT_NAME}_mm-test -v ${WORKSPACE}/src:/go/src/", image: 'mattermost/mattermost-build-server:feb-28-2019') { withDockerContainer(args: "-u root --privileged --net ${COMPOSE_PROJECT_NAME}_mm-test -v ${WORKSPACE}/src:/go/src/", image: 'mattermost/mattermost-build-server:feb-28-2019') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
cd /go/src/github.com/mattermost/mattermost-server cd /go/src/github.com/mattermost/mattermost-server
echo "Setting up config for db migration" echo "Setting up config for db migration"
export MM_SQLSETTINGS_DATASOURCE=\"mmuser:mostest@tcp(mysql:3306)/migrated?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s\" export MM_SQLSETTINGS_DATASOURCE=\"mmuser:mostest@tcp(mysql:3306)/migrated?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s\"
export MM_SQLSETTINGS_DRIVERNAME=\"mysql\" export MM_SQLSETTINGS_DRIVERNAME=\"mysql\"
make ARGS="config get SqlSettings.DataSource" run-cli make ARGS="config get SqlSettings.DataSource" run-cli
echo "Running the migration" echo "Running the migration"
make ARGS="version" run-cli make ARGS="version" run-cli
echo "Setting up config for fresh db setup" echo "Setting up config for fresh db setup"
export MM_SQLSETTINGS_DATASOURCE=\"mmuser:mostest@tcp(mysql:3306)/latest?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s\" export MM_SQLSETTINGS_DATASOURCE=\"mmuser:mostest@tcp(mysql:3306)/latest?charset=utf8mb4,utf8&readTimeout=30s&writeTimeout=30s\"
make ARGS="config get SqlSettings.DataSource" run-cli make ARGS="config get SqlSettings.DataSource" run-cli
echo "Setting up fresh db" echo "Setting up fresh db"
make ARGS="version" run-cli make ARGS="version" run-cli
""" """
} }
} }
dir('src/github.com/mattermost/mattermost-server') { dir('src/github.com/mattermost/mattermost-server') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
echo "Generating dump" echo "Generating dump"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysqldump --skip-opt --no-data --compact -u root -pmostest migrated > migrated.sql /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysqldump --skip-opt --no-data --compact -u root -pmostest migrated > migrated.sql
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysqldump --skip-opt --no-data --compact -u root -pmostest latest > latest.sql /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysqldump --skip-opt --no-data --compact -u root -pmostest latest > latest.sql
echo "Removing databases created for db comparison" echo "Removing databases created for db comparison"
/usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysql -uroot -pmostest -e \"DROP DATABASE migrated; DROP DATABASE latest\" /usr/local/bin/docker-compose --no-ansi -f build/docker-compose.yml exec -T mysql mysql -uroot -pmostest -e \"DROP DATABASE migrated; DROP DATABASE latest\"
echo "Generating diff" echo "Generating diff"
diff migrated.sql latest.sql > diff.txt diff migrated.sql latest.sql > diff.txt
export diffErrorCode=\$? export diffErrorCode=\$?
if [ \$diffErrorCode -eq 0 ]; then echo \"Both schemas are same\";else cat diff.txt; fi if [ \$diffErrorCode -eq 0 ]; then echo \"Both schemas are same\";else cat diff.txt; fi
exit \$diffErrorCode exit \$diffErrorCode
""" """
} }
} }
withDockerContainer(args: "-u root --privileged --net ${COMPOSE_PROJECT_NAME}_mm-test -v ${WORKSPACE}/src:/go/src/", image: 'mattermost/mattermost-build-server:feb-28-2019') { withDockerContainer(args: "-u root --privileged --net ${COMPOSE_PROJECT_NAME}_mm-test -v ${WORKSPACE}/src:/go/src/", image: 'mattermost/mattermost-build-server:feb-28-2019') {
ansiColor('xterm') { ansiColor('xterm') {
sh """ sh """
cd /go/src/github.com/mattermost/mattermost-server cd /go/src/github.com/mattermost/mattermost-server
mkdir -p client/plugins mkdir -p client/plugins
cat config/config.json cat config/config.json
make test-server BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' TESTFLAGS= TESTFLAGSEE= make test-server BUILD_NUMBER='${BRANCH_NAME}-${BUILD_NUMBER}' TESTFLAGS= TESTFLAGSEE=
""" """
} }
withCredentials([string(credentialsId: 'CODECOV_TOKEN', variable: 'CODECOV')]) { withCredentials([string(credentialsId: 'CODECOV_TOKEN', variable: 'CODECOV')]) {
sh ''' sh '''
cd /go/src/github.com/mattermost/mattermost-server cd /go/src/github.com/mattermost/mattermost-server
curl -s https://codecov.io/bash | bash -s - -t $CODECOV || echo 'Codecov failed to upload' curl -s https://codecov.io/bash | bash -s - -t $CODECOV || echo 'Codecov failed to upload'
''' '''
} }
} }
} }
} }
}
stage('Push to S3') { post {
stages { always {
stage('Pull request') { dir('src/github.com/mattermost/mattermost-server') {
when { junit allowEmptyResults: true, healthScaleFactor: 0.0, testResults: 'report*.xml'
allOf { archiveArtifacts 'report*.xml'
expression { env.CHANGE_ID != null } }
expression { env.CHANGE_TARGET != null } dir('src/github.com/mattermost/mattermost-server/build') {
} ansiColor('xterm') {
} sh """
steps { # Capture docker logs
dir('src/github.com/mattermost/mattermost-server/dist') { docker-compose logs --tail="all" -t --no-color > docker-compose_logs
step([$class: 'S3BucketPublisher', dontWaitForConcurrentBuildCompletion: false, entries: [[ docker ps -a --no-trunc > docker_ps
bucket: "releases.mattermost.com/mattermost-platform-pr/${CHANGE_ID}", docker stats -a --no-stream > docker_stats
excludedFile: '', tar -czvf docker_logs.tar.gz docker-compose_logs docker_ps docker_stats
flatten: true,
gzipFiles: false,
keepForever: false,
managedArtifacts: false,
noUploadOnFailure: true,
selectedRegion: 'us-east-1',
showDirectlyInBrowser: false,
sourceFile: '*.tar.gz',
storageClass: 'STANDARD',
uploadFromSlave: false,
useServerSideEncryption: false,
userMetadata: [[key: 'Cache-Control', value: 'no-cache']]
], [
bucket: "releases.mattermost.com/mattermost-platform-pr/${CHANGE_BRANCH}",
excludedFile: '',
flatten: true,
gzipFiles: false,
keepForever: false,
managedArtifacts: false,
noUploadOnFailure: true,
selectedRegion: 'us-east-1',
showDirectlyInBrowser: false,
sourceFile: '*.tar.gz',
storageClass: 'STANDARD',
uploadFromSlave: false,
useServerSideEncryption: false,
userMetadata: [[key: 'Cache-Control', value: 'no-cache']]
]], profileName: 'Releases', userMetadAta: []])
}
}
}
stage('Branch') {
when {
expression { env.CHANGE_ID == null }
}
steps {
dir('src/github.com/mattermost/mattermost-server/dist') {
step([$class: 'S3BucketPublisher', dontWaitForConcurrentBuildCompletion: false, entries: [[
bucket: "releases.mattermost.com/mattermost-platform-pr/${BRANCH_NAME}",
excludedFile: '',
flatten: true,
gzipFiles: false,
keepForever: false,
managedArtifacts: false,
noUploadOnFailure: true,
selectedRegion: 'us-east-1',
showDirectlyInBrowser: false,
sourceFile: '*.tar.gz',
storageClass: 'STANDARD',
uploadFromSlave: false,
useServerSideEncryption: false,
userMetadata: [[key: 'Cache-Control', value: 'no-cache']]
]], profileName: 'Releases', userMetadAta: []])
}
}
}
}
}
stage('Clean checkout') { docker-compose --no-ansi down -v
when { """
expression { env.CHANGE_ID != null } }
} archiveArtifacts 'docker_logs.tar.gz'
// We need to perform a clean checkout here to ge the original git commit hash from the PR }
// Jenkins now merges master in top of the PR and this generate a new git hash }
// We need to do that to build the docker image based on the original git commit and then this will be used by cleanup {
// mattermod to update the test server. cleanWs notFailBuild: true
steps { }
sh """ }
mkdir -p /tmp/mattermost-server
"""
dir('/tmp/mattermost-server') {
checkout([$class: 'GitSCM', branches: [[name: 'FETCH_HEAD']],
doGenerateSubmoduleConfigurations: false, extensions: [],
submoduleCfg: [], userRemoteConfigs: [
[refspec: "+refs/pull/${CHANGE_ID}/head:refs/remotes/origin/PR-${CHANGE_ID}",
credentialsId: "310159d3-f7c5-4f5d-bfa1-151e3ef2db57",url: "https://github.com/mattermost/mattermost-server.git"]]])
sh 'git rev-parse --short HEAD'
}
}
}
stage('Build Docker Image') {
environment {
GIT_COMMIT_SHORT = sh(
script: "cd /tmp/mattermost-server && printf \$(git rev-parse --short HEAD)",
returnStdout: true
)
}
when {
expression { env.CHANGE_ID != null }
}
steps {
dir('src/github.com/mattermost/mattermost-server') {
withCredentials([usernamePassword(credentialsId: 'matterbuild-docker-hub', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) {
sh 'docker login --username ${DOCKER_USER} --password ${DOCKER_PASS}'
sh """
docker build --no-cache --build-arg MM_PACKAGE=https://releases.mattermost.com/mattermost-platform-pr/${CHANGE_ID}/mattermost-enterprise-linux-amd64.tar.gz -t mattermost/mattermost-enterprise-edition:${GIT_COMMIT_SHORT} build
docker push mattermost/mattermost-enterprise-edition:${GIT_COMMIT_SHORT}
docker logout
"""
}
}
}
}
}
post {
always {
dir('src/github.com/mattermost/mattermost-server') {
junit allowEmptyResults: true, healthScaleFactor: 0.0, testResults: 'report*.xml'
archiveArtifacts 'report*.xml'
}
dir('src/github.com/mattermost/mattermost-server/build') {
ansiColor('xterm') {
sh """
# Capture docker logs
docker-compose logs --tail="all" -t --no-color > docker-compose_logs
docker ps -a --no-trunc > docker_ps
docker stats -a --no-stream > docker_stats
tar -czvf docker_logs.tar.gz docker-compose_logs docker_ps docker_stats
docker-compose --no-ansi down -v
"""
}
archiveArtifacts 'docker_logs.tar.gz'
}
}
cleanup {
cleanWs notFailBuild: true
}
}
} }

Просмотреть файл

@@ -21,6 +21,8 @@ import (
"github.com/mattermost/viper" "github.com/mattermost/viper"
) )
const noSettingsNamed = "unable to find a setting named: %s"
var ConfigCmd = &cobra.Command{ var ConfigCmd = &cobra.Command{
Use: "config", Use: "config",
Short: "Configuration", Short: "Configuration",
@@ -296,7 +298,7 @@ func updateConfigValue(configSetting string, newVal []string, oldConfig, newConf
func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal []string) error { func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal []string) error {
res, ok := configMap[configSettings[0]] res, ok := configMap[configSettings[0]]
if !ok { if !ok {
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0]) return fmt.Errorf(noSettingsNamed, configSettings[0])
} }
value := reflect.ValueOf(res) value := reflect.ValueOf(res)
@@ -308,6 +310,9 @@ func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal
if len(configSettings) == 1 { if len(configSettings) == 1 {
return errors.New("unable to set multiple settings at once") return errors.New("unable to set multiple settings at once")
} }
if value.Len() == 0 {
return fmt.Errorf(noSettingsNamed, configSettings[1])
}
return UpdateMap(res.(map[string]interface{}), configSettings[1:], newVal) return UpdateMap(res.(map[string]interface{}), configSettings[1:], newVal)
case reflect.Int: case reflect.Int:
@@ -319,7 +324,7 @@ func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal
configMap[configSettings[0]] = val configMap[configSettings[0]] = val
return nil return nil
} }
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0]) return fmt.Errorf(noSettingsNamed, configSettings[0])
case reflect.Int64: case reflect.Int64:
if len(configSettings) == 1 { if len(configSettings) == 1 {
@@ -330,7 +335,7 @@ func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal
configMap[configSettings[0]] = int64(val) configMap[configSettings[0]] = int64(val)
return nil return nil
} }
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0]) return fmt.Errorf(noSettingsNamed, configSettings[0])
case reflect.Bool: case reflect.Bool:
if len(configSettings) == 1 { if len(configSettings) == 1 {
@@ -341,21 +346,21 @@ func UpdateMap(configMap map[string]interface{}, configSettings []string, newVal
configMap[configSettings[0]] = val configMap[configSettings[0]] = val
return nil return nil
} }
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0]) return fmt.Errorf(noSettingsNamed, configSettings[0])
case reflect.String: case reflect.String:
if len(configSettings) == 1 { if len(configSettings) == 1 {
configMap[configSettings[0]] = newVal[0] configMap[configSettings[0]] = newVal[0]
return nil return nil
} }
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0]) return fmt.Errorf(noSettingsNamed, configSettings[0])
case reflect.Slice: case reflect.Slice:
if len(configSettings) == 1 { if len(configSettings) == 1 {
configMap[configSettings[0]] = newVal configMap[configSettings[0]] = newVal
return nil return nil
} }
return fmt.Errorf("unable to find a setting with that name %s", configSettings[0]) return fmt.Errorf(noSettingsNamed, configSettings[0])
default: default:
return errors.New("type not supported yet") return errors.New("type not supported yet")

Просмотреть файл

@@ -141,6 +141,12 @@ func TestConfigSet(t *testing.T) {
assert.NotContains(t, string(output), "invalid-key") assert.NotContains(t, string(output), "invalid-key")
}) })
t.Run("Error when the parameter of an unknown plugin is set", func(t *testing.T) {
output, err := th.RunCommandWithOutput(t, "config", "set", "PluginSettings.Plugins.someplugin", "true")
assert.Error(t, err)
assert.NotContains(t, string(output), "panic")
})
t.Run("Error when the wrong locale is set", func(t *testing.T) { t.Run("Error when the wrong locale is set", func(t *testing.T) {
th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es") th.CheckCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "es")
assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid-key")) assert.Error(t, th.RunCommand(t, "config", "set", "LocalizationSettings.DefaultServerLocale", "invalid-key"))

Просмотреть файл

@@ -8,6 +8,7 @@ import (
"database/sql" "database/sql"
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"regexp"
"strings" "strings"
"github.com/jmoiron/sqlx" "github.com/jmoiron/sqlx"
@@ -22,6 +23,8 @@ import (
_ "github.com/lib/pq" _ "github.com/lib/pq"
) )
var tcpStripper = regexp.MustCompile(`@tcp\((.*)\)`)
// DatabaseStore is a config store backed by a database. // DatabaseStore is a config store backed by a database.
type DatabaseStore struct { type DatabaseStore struct {
commonStore commonStore
@@ -101,23 +104,22 @@ func initializeConfigurationsTable(db *sqlx.DB) error {
// By contrast, a Postgres DSN is returned unmodified. // By contrast, a Postgres DSN is returned unmodified.
func parseDSN(dsn string) (string, string, error) { func parseDSN(dsn string) (string, string, error) {
// Treat the DSN as the URL that it is. // Treat the DSN as the URL that it is.
u, err := url.Parse(dsn) s := strings.SplitN(dsn, "://", 2)
if err != nil { if len(s) != 2 {
return "", "", errors.Wrap(err, "failed to parse DSN as URL") errors.New("failed to parse DSN as URL")
} }
scheme := u.Scheme scheme := s[0]
switch scheme { switch scheme {
case "mysql": case "mysql":
// Strip off the mysql:// for the dsn with which to connect. // Strip off the mysql:// for the dsn with which to connect.
u.Scheme = "" dsn = s[1]
dsn = strings.TrimPrefix(u.String(), "//")
case "postgres": case "postgres":
// No changes required // No changes required
default: default:
return "", "", errors.Wrapf(err, "unsupported scheme %s", scheme) return "", "", errors.Errorf("unsupported scheme %s", scheme)
} }
return scheme, dsn, nil return scheme, dsn, nil
@@ -293,7 +295,11 @@ func (ds *DatabaseStore) RemoveFile(name string) error {
// String returns the path to the database backing the config, masking the password. // String returns the path to the database backing the config, masking the password.
func (ds *DatabaseStore) String() string { func (ds *DatabaseStore) String() string {
u, _ := url.Parse(ds.originalDsn) // Remove @tcp and the parentheses from the host and parse the rest as a URL
u, err := url.Parse(tcpStripper.ReplaceAllString(ds.originalDsn, `@$1`))
if err != nil {
return "(omitted due to error parsing the DSN)"
}
// Strip out the password to avoid leaking in logs. // Strip out the password to avoid leaking in logs.
u.User = url.User(u.User.Username()) u.User = url.User(u.User.Username())

Просмотреть файл

@@ -6,7 +6,6 @@ package config_test
import ( import (
"bytes" "bytes"
"fmt" "fmt"
"net/url"
"os" "os"
"strings" "strings"
"testing" "testing"
@@ -137,6 +136,11 @@ func TestDatabaseStoreNew(t *testing.T) {
_, err := config.NewDatabaseStore("invalid") _, err := config.NewDatabaseStore("invalid")
require.Error(t, err) require.Error(t, err)
}) })
t.Run("unsupported scheme with valid data source", func(t *testing.T) {
_, err := config.NewDatabaseStore(fmt.Sprintf("invalid://%s", *sqlSettings.DataSource))
require.Error(t, err)
})
} }
func TestDatabaseStoreGet(t *testing.T) { func TestDatabaseStoreGet(t *testing.T) {
@@ -930,14 +934,11 @@ func TestDatabaseStoreString(t *testing.T) {
sqlSettings := mainHelper.GetSqlSettings() sqlSettings := mainHelper.GetSqlSettings()
ds, err := config.NewDatabaseStore(fmt.Sprintf("%s://%s", *sqlSettings.DriverName, *sqlSettings.DataSource)) ds, err := config.NewDatabaseStore(fmt.Sprintf("%s://%s", *sqlSettings.DriverName, *sqlSettings.DataSource))
require.NoError(t, err) require.NoError(t, err)
require.NotNil(t, ds)
defer ds.Close() defer ds.Close()
actualStringURL, err := url.Parse(ds.String()) maskedDSN := ds.String()
require.NoError(t, err) assert.True(t, strings.HasPrefix(maskedDSN, "mysql://"))
assert.True(t, strings.Contains(maskedDSN, "mmuser"))
assert.Equal(t, *sqlSettings.DriverName, actualStringURL.Scheme) assert.False(t, strings.Contains(maskedDSN, "mostest"))
actualUsername := actualStringURL.User.Username()
actualPassword, _ := actualStringURL.User.Password()
assert.NotEmpty(t, actualUsername)
assert.Empty(t, actualPassword, "should mask password")
} }

Просмотреть файл

@@ -44,4 +44,5 @@ type MetricsInterface interface {
IncrementPostsSearchCounter() IncrementPostsSearchCounter()
ObservePostsSearchDuration(elapsed float64) ObservePostsSearchDuration(elapsed float64)
ObserveStoreMethodDuration(method string, success string, elapsed float64) ObserveStoreMethodDuration(method string, success string, elapsed float64)
ObserveApiEndpointDuration(endpoint string, elapsed float64)
} }

Просмотреть файл

@@ -395,26 +395,31 @@ func (g *hooksRPCClient) FileWillBeUploaded(c *Context, info *model.FileInfo, fi
serveIOReader(file, uploadedFileConnection) serveIOReader(file, uploadedFileConnection)
}() }()
replacementDone := make(chan bool)
replacementFileStreamId := g.muxBroker.NextId() replacementFileStreamId := g.muxBroker.NextId()
go func() { go func() {
defer close(replacementDone)
replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId) replacementFileConnection, err := g.muxBroker.Accept(replacementFileStreamId)
if err != nil { if err != nil {
g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err)) g.log.Error("Plugin failed to serve replacement file stream. MuxBroker could not Accept connection", mlog.Err(err))
return return
} }
defer replacementFileConnection.Close() defer replacementFileConnection.Close()
if _, err := io.Copy(output, replacementFileConnection); err != nil && err != io.EOF { if _, err := io.Copy(output, replacementFileConnection); err != nil {
g.log.Error("Error reading replacement file.", mlog.Err(err)) g.log.Error("Error reading replacement file.", mlog.Err(err))
} }
}() }()
_args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId} _args := &Z_FileWillBeUploadedArgs{c, info, uploadedFileStreamId, replacementFileStreamId}
_returns := &Z_FileWillBeUploadedReturns{A: _args.B} _returns := &Z_FileWillBeUploadedReturns{A: _args.B}
if g.implemented[FileWillBeUploadedId] { if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil {
if err := g.client.Call("Plugin.FileWillBeUploaded", _args, _returns); err != nil { g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err))
g.log.Error("RPC call FileWillBeUploaded to plugin failed.", mlog.Err(err))
}
} }
// Ensure the io.Copy from the replacementFileConnection above completes.
<-replacementDone
return _returns.A, _returns.B return _returns.A, _returns.B
} }

Просмотреть файл

@@ -6,6 +6,9 @@ package web
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"reflect"
"runtime"
"strings"
"time" "time"
"github.com/NYTimes/gziphandler" "github.com/NYTimes/gziphandler"
@@ -16,10 +19,20 @@ import (
"github.com/mattermost/mattermost-server/utils" "github.com/mattermost/mattermost-server/utils"
) )
func GetHandlerName(h func(*Context, http.ResponseWriter, *http.Request)) string {
handlerName := runtime.FuncForPC(reflect.ValueOf(h).Pointer()).Name()
pos := strings.LastIndex(handlerName, ".")
if pos != -1 && len(handlerName) > pos {
handlerName = handlerName[pos+1:]
}
return handlerName
}
func (w *Web) NewHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler { func (w *Web) NewHandler(h func(*Context, http.ResponseWriter, *http.Request)) http.Handler {
return &Handler{ return &Handler{
GetGlobalAppOptions: w.GetGlobalAppOptions, GetGlobalAppOptions: w.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: false, TrustRequester: false,
RequireMfa: false, RequireMfa: false,
@@ -35,6 +48,7 @@ func (w *Web) NewStaticHandler(h func(*Context, http.ResponseWriter, *http.Reque
return &Handler{ return &Handler{
GetGlobalAppOptions: w.GetGlobalAppOptions, GetGlobalAppOptions: w.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: false, TrustRequester: false,
RequireMfa: false, RequireMfa: false,
@@ -47,6 +61,7 @@ func (w *Web) NewStaticHandler(h func(*Context, http.ResponseWriter, *http.Reque
type Handler struct { type Handler struct {
GetGlobalAppOptions app.AppOptionCreator GetGlobalAppOptions app.AppOptionCreator
HandleFunc func(*Context, http.ResponseWriter, *http.Request) HandleFunc func(*Context, http.ResponseWriter, *http.Request)
HandlerName string
RequireSession bool RequireSession bool
TrustRequester bool TrustRequester bool
RequireMfa bool RequireMfa bool
@@ -192,6 +207,7 @@ func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != model.API_URL_SUFFIX+"/websocket" { if r.URL.Path != model.API_URL_SUFFIX+"/websocket" {
elapsed := float64(time.Since(now)) / float64(time.Second) elapsed := float64(time.Since(now)) / float64(time.Second)
c.App.Metrics.ObserveHttpRequestDuration(elapsed) c.App.Metrics.ObserveHttpRequestDuration(elapsed)
c.App.Metrics.ObserveApiEndpointDuration(h.HandlerName, elapsed)
} }
} }
} }
@@ -249,6 +265,7 @@ func (w *Web) ApiHandler(h func(*Context, http.ResponseWriter, *http.Request)) h
handler := &Handler{ handler := &Handler{
GetGlobalAppOptions: w.GetGlobalAppOptions, GetGlobalAppOptions: w.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: false, TrustRequester: false,
RequireMfa: false, RequireMfa: false,
@@ -267,6 +284,7 @@ func (w *Web) ApiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *ht
handler := &Handler{ handler := &Handler{
GetGlobalAppOptions: w.GetGlobalAppOptions, GetGlobalAppOptions: w.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: true, TrustRequester: true,
RequireMfa: false, RequireMfa: false,
@@ -284,6 +302,7 @@ func (w *Web) ApiSessionRequired(h func(*Context, http.ResponseWriter, *http.Req
handler := &Handler{ handler := &Handler{
GetGlobalAppOptions: w.GetGlobalAppOptions, GetGlobalAppOptions: w.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: GetHandlerName(h),
RequireSession: true, RequireSession: true,
TrustRequester: false, TrustRequester: false,
RequireMfa: true, RequireMfa: true,
@@ -302,6 +321,7 @@ func (w *Web) apiHandlerTrustRequester(h func(*Context, http.ResponseWriter, *ht
handler := &Handler{ handler := &Handler{
GetGlobalAppOptions: w.GetGlobalAppOptions, GetGlobalAppOptions: w.GetGlobalAppOptions,
HandleFunc: h, HandleFunc: h,
HandlerName: GetHandlerName(h),
RequireSession: false, RequireSession: false,
TrustRequester: true, TrustRequester: true,
RequireMfa: false, RequireMfa: false,