Merge branch 'master' into mark-as-unread
Этот коммит содержится в:
@@ -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,
|
||||||
|
|||||||
21
api4/user.go
21
api4/user.go
@@ -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,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user