diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1e41c4052..27186aa83 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -11,6 +11,7 @@ on: env: BRANCH_NAME: ${{ github.head_ref || github.ref_name }} EXCLUDE_ENTERPRISE: true + GO_VERSION: 1.24.6 jobs: webapp-test: @@ -38,14 +39,118 @@ jobs: run: cd focalboard; make webapp-ci - name: set up golangci-lint - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.64.8 + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6 - name: Lint & test server run: cd focalboard; make server-ci - + dist: - uses: mattermost/actions-workflows/.github/workflows/plugin-dist-pr.yml@main - secrets: inherit - with: - dist-target: "dist-linux" - s3-prefix: "mattermost-plugin-boards" \ No newline at end of file + runs-on: ubuntu-22.04 + needs: + - webapp-test + permissions: + id-token: write + steps: + - name: Checkout + uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 + with: + path: "focalboard" + fetch-depth: "0" + + - name: Set up Go + uses: actions/setup-go@3041bf56c941b39c61721a86cd11f3bb1338122a # v5.2.0 + with: + go-version: "${{ env.GO_VERSION }}" + cache: true + + - name: Setup Node + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version-file: focalboard/.nvmrc + cache: "npm" + cache-dependency-path: focalboard/webapp/package-lock.json + + - name: Cache webapp node modules + id: cache-webapp-node-modules + uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 + with: + path: focalboard/webapp/node_modules + key: ${{ runner.os }}-webapp-node-modules-${{ hashFiles('focalboard/webapp/package-lock.json') }} + restore-keys: ${{ runner.os }}-webapp-node-modules- + + - name: Setup webapp npm deps + if: steps.cache-webapp-node-modules.outputs.cache-hit != 'true' + env: + NODE_ENV: development + run: | + cd focalboard/webapp + npm install --ignore-scripts --no-save + + - name: ci/setup-chainctl + uses: chainguard-dev/setup-chainctl@v0.3.2 + with: + identity: ${{ secrets.CHAINGUARD_IDENTITY }} + + - name: ci/setup-build-tools + run: | + echo "Setting up build tools..." + cd focalboard + mkdir -p build/bin + cd build/manifest && go build -o ../bin/manifest + cd ../pluginctl && go build -o ../bin/pluginctl + + - name: Build both distributions + env: + GO_VERSION: ${{ env.GO_VERSION }} + run: cd focalboard; make dist-all + + - name: ci/display-signing-parameters + if: github.event_name == 'pull_request' + run: | + cd focalboard + echo "📦 Plugin Artifact Signing Parameters" + echo "====================================" + + # Extract plugin version using manifest tool (handles git versioning) + PLUGIN_VERSION=$(build/bin/manifest version) + # Add 'v' prefix for consistency with other plugin packages + if [[ ! $PLUGIN_VERSION == v* ]]; then + PLUGIN_VERSION="v${PLUGIN_VERSION}" + fi + SHORT_SHA=$(echo "${{ github.sha }}" | cut -c1-7) + + echo "" + echo "To sign artifacts from this PR, run the following command:" + echo "" + echo "gh workflow run sign-plugin-pr-artifacts.yaml \\" + echo " --repo mattermost/delivery-platform \\" + echo " --field repository_full_name=\"${{ github.repository }}\" \\" + echo " --field pr_number=\"${{ github.event.number }}\" \\" + echo " --field commit_sha=\"${{ github.sha }}\" \\" + echo " --field run_id=\"${{ github.run_id }}\" \\" + echo " --field plugin_version=\"${PLUGIN_VERSION}\" \\" + echo " --field include_fips=true" + echo "" + echo "Or use the GitHub web interface with these values:" + echo "- Repository Full Name: ${{ github.repository }}" + echo "- PR Number: ${{ github.event.number }}" + echo "- Commit SHA: ${{ github.sha }}" + echo "- Run ID: ${{ github.run_id }}" + echo "- Plugin Version: ${PLUGIN_VERSION}" + echo "- Include FIPS: true" + echo "" + echo "Expected artifact naming:" + echo "- mattermost-plugin-boards-${PLUGIN_VERSION}+${SHORT_SHA}-linux-amd64.tar.gz" + echo "- mattermost-plugin-boards-${PLUGIN_VERSION}+${SHORT_SHA}-fips-linux-amd64.tar.gz" + echo "" + echo "Artifacts will be available at:" + echo "https://plugins.releases.mattermost.com/pr/mattermost-plugin-boards/pr-${{ github.event.number }}-${SHORT_SHA}/" + + - name: Upload all artifacts + uses: actions/upload-artifact@1746f4ab65b179e0ea60a494b83293b640dd5bba # v4.5.0 + with: + name: all-plugin-artifacts + path: | + focalboard/dist/*.tar.gz + focalboard/dist-fips/*.tar.gz + retention-days: 7 diff --git a/.github/workflows/lint-server.yml b/.github/workflows/lint-server.yml index e0019ea52..c38f1af5b 100644 --- a/.github/workflows/lint-server.yml +++ b/.github/workflows/lint-server.yml @@ -35,7 +35,7 @@ jobs: with: go-version-file: focalboard/go.mod - name: set up golangci-lint - run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.64.8 + run: curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6 - name: lint run: | cd focalboard diff --git a/.gitignore b/.gitignore index e2a6256d8..8efcba4e8 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,8 @@ webapp/node_modules webapp/dist webapp/pack dist +dist-fips +.build-cache/ package bin debug diff --git a/Makefile b/Makefile index 7f4d140a3..c12e5e57f 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: prebuild clean cleanall ci server server-linux server-mac server-win server-linux-package generate watch-server webapp mac-app win-app-wpf linux-app modd-precheck templates-archive +.PHONY: prebuild clean cleanall ci server server-mac server-linux server-win server-linux-package generate watch-server webapp mac-app win-app-wpf linux-app modd-precheck templates-archive dist-all PACKAGE_FOLDER = focalboard @@ -38,7 +38,14 @@ MATTERMOST_PLUGINS_PATH=$(MM_SERVER_PATH)/plugins BOARD_PLUGIN_PATH=$(MATTERMOST_PLUGINS_PATH)/boards PLUGIN_NAME=boards -export GO111MODULE=on +# FIPS Support - similar to mattermost server +# To build FIPS-compliant plugin: make dist-fips +# Requires Docker to be installed and running +FIPS_IMAGE ?= cgr.dev/mattermost.com/go-msft-fips:1.24.6@sha256:b94d424ab26b590163634001b22242ceac6f5d76bfbbaa77b6f0dda97220c717 + +# We need to export GOBIN to allow it to be set +# for processes spawned from the Makefile +export GOBIN ?= $(PWD)/bin ASSETS_DIR ?= assets @@ -51,7 +58,15 @@ default: all # Verify environment, and define PLUGIN_ID, PLUGIN_VERSION, HAS_SERVER and HAS_WEBAPP as needed. include build/setup.mk -BUNDLE_NAME ?= $(PLUGIN_NAME)-$(PLUGIN_VERSION).tar.gz +BUNDLE_NAME ?= $(PLUGIN_ID)-$(PLUGIN_VERSION).tar.gz + +# Helper function to copy common bundle files +define copy_bundle_files + $(if $(wildcard LICENSE.txt),cp -r LICENSE.txt $(1)/$(PLUGIN_ID)/) + $(if $(wildcard NOTICE.txt),cp -r NOTICE.txt $(1)/$(PLUGIN_ID)/) + $(if $(wildcard $(ASSETS_DIR)/.),cp -r $(ASSETS_DIR) $(1)/$(PLUGIN_ID)/) + $(if $(HAS_PUBLIC),cp -r public $(1)/$(PLUGIN_ID)/public/) +endef # Include custom makefile, if present ifneq ($(wildcard build/custom.mk),) @@ -114,6 +129,48 @@ else endif endif +## Builds the server with FIPS compliance using Docker (requires Docker) +.PHONY: server-fips +server-fips: templates-archive +ifneq ($(HAS_SERVER),) + @echo Building FIPS-compliant plugin server binaries + mkdir -p server/dist-fips + @echo "Setting up FIPS build environment..." + + # Docker authentication is handled by CI (setup-chainctl) + @if ! docker manifest inspect $(FIPS_IMAGE) >/dev/null 2>&1; then \ + echo "Docker authentication failed. Ensure setup-chainctl configured Docker authentication."; \ + echo "Trying fallback authentication if credentials are available..."; \ + if [ -n "$(CHAINGUARD_DEV_USERNAME)" ] && [ -n "$(CHAINGUARD_DEV_TOKEN)" ]; then \ + echo "Using username/token authentication..."; \ + echo "$(CHAINGUARD_DEV_TOKEN)" | docker login cgr.dev --username "$(CHAINGUARD_DEV_USERNAME)" --password-stdin; \ + else \ + echo "Warning: No authentication available. FIPS build may fail."; \ + fi; \ + else \ + echo "✅ Docker authentication is working"; \ + fi + + # Create local cache directory for CI/ACT compatibility + mkdir -p $(PWD)/.build-cache + + # Try FIPS build with error handling + @if docker run --rm \ + --entrypoint="" \ + -v $(PWD):/plugin \ + -v $(PWD)/.build-cache:/root/.cache \ + -w /plugin/server \ + $(FIPS_IMAGE) \ + sh -c "CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -tags fips -ldflags '$(LDFLAGS)' -trimpath -buildvcs=false -o dist-fips/plugin-linux-amd64-fips"; then \ + echo "FIPS plugin server build completed: server/dist-fips/plugin-linux-amd64-fips"; \ + else \ + echo "FIPS build failed - likely authentication issue with $(FIPS_IMAGE)"; \ + echo "Creating placeholder to indicate FIPS build was attempted but failed"; \ + echo "FIPS_BUILD_FAILED" > server/dist-fips/FIPS_BUILD_FAILED.txt; \ + exit 1; \ + fi +endif + ## Builds the server, if it exists, for Linux architectures only. .PHONY: server-linux server-linux: templates-archive @@ -150,33 +207,62 @@ endif ## Generates a tar bundle of the plugin for install. .PHONY: bundle bundle: - rm -rf dist/ - mkdir -p dist/$(PLUGIN_NAME) - cp $(MANIFEST_FILE) dist/$(PLUGIN_NAME)/ - cp -r webapp/pack dist/$(PLUGIN_NAME)/ -ifneq ($(wildcard LICENSE.txt),) - cp -r LICENSE.txt dist/$(PLUGIN_NAME)/ -endif -ifneq ($(wildcard NOTICE.txt),) - cp -r NOTICE.txt dist/$(PLUGIN_NAME)/ -endif -ifneq ($(wildcard $(ASSETS_DIR)/.),) - cp -r $(ASSETS_DIR) dist/$(PLUGIN_NAME)/ + rm -rf dist/$(PLUGIN_ID) + mkdir -p dist/$(PLUGIN_ID) + cp $(MANIFEST_FILE) dist/$(PLUGIN_ID)/ + cp -r webapp/pack dist/$(PLUGIN_ID)/ + $(call copy_bundle_files,dist) +ifneq ($(HAS_SERVER),) + mkdir -p dist/$(PLUGIN_ID)/server + cp -r server/dist dist/$(PLUGIN_ID)/server/ endif -ifneq ($(HAS_PUBLIC),) - cp -r public dist/$(PLUGIN_NAME)/public/ +ifneq ($(HAS_WEBAPP),) + mkdir -p dist/$(PLUGIN_ID)/webapp + cp -r webapp/dist dist/$(PLUGIN_ID)/webapp/ endif + cd dist && tar -cvzf $(BUNDLE_NAME) $(PLUGIN_ID) + + @echo "==> Normal plugin built at: dist/$(BUNDLE_NAME)" + +## Generates a tar bundle of the FIPS plugin for install. +.PHONY: bundle-fips +bundle-fips: + rm -rf dist-fips/ + mkdir -p dist-fips/$(PLUGIN_ID) + ./build/bin/manifest dist-fips + $(call copy_bundle_files,dist-fips) ifneq ($(HAS_SERVER),) - mkdir -p dist/$(PLUGIN_NAME)/server - cp -r server/dist dist/$(PLUGIN_NAME)/server/ + mkdir -p dist-fips/$(PLUGIN_ID)/server/dist + # Copy FIPS binaries but rename them to standard names for server compatibility + if [ -f server/dist-fips/plugin-linux-amd64-fips ]; then \ + cp server/dist-fips/plugin-linux-amd64-fips dist-fips/$(PLUGIN_ID)/server/dist/plugin-linux-amd64; \ + fi + # Copy any other FIPS binaries and rename them + for file in server/dist-fips/plugin-*-fips*; do \ + if [ -f "$$file" ]; then \ + target=$$(basename "$$file" | sed 's/-fips//g'); \ + cp "$$file" "dist-fips/$(PLUGIN_ID)/server/dist/$$target"; \ + fi; \ + done endif ifneq ($(HAS_WEBAPP),) - mkdir -p dist/$(PLUGIN_NAME)/webapp - cp -r webapp/dist dist/$(PLUGIN_NAME)/webapp/ + if [ -d webapp/dist ]; then \ + mkdir -p dist-fips/$(PLUGIN_ID)/webapp && \ + cp -r webapp/dist dist-fips/$(PLUGIN_ID)/webapp/; \ + else \ + echo "Error: webapp/dist not found, but HAS_WEBAPP is set. Run 'make webapp' first."; \ + exit 1; \ + fi +endif + # Use webpack pack for webapp bundle + cp -r webapp/pack dist-fips/$(PLUGIN_ID)/ +ifeq ($(shell uname),Darwin) + cd dist-fips && tar --disable-copyfile -cvzf $(PLUGIN_ID)-$(PLUGIN_VERSION)-fips.tar.gz $(PLUGIN_ID) +else + cd dist-fips && tar -cvzf $(PLUGIN_ID)-$(PLUGIN_VERSION)-fips.tar.gz $(PLUGIN_ID) endif - cd dist && tar -cvzf $(BUNDLE_NAME) $(PLUGIN_NAME) - @echo plugin built at: dist/$(BUNDLE_NAME) + @echo "==> FIPS plugin built at: dist-fips/$(PLUGIN_ID)-$(PLUGIN_VERSION)-fips.tar.gz" info: ## Display build information @echo "Build Number: $(BUILD_NUMBER)" @@ -190,6 +276,25 @@ info: ## Display build information .PHONY: dist dist: apply server webapp bundle +## Builds and bundles the FIPS plugin. +.PHONY: dist-fips +dist-fips: apply server-fips webapp bundle-fips + +## Builds both normal and FIPS distributions. +.PHONY: dist-all +dist-all: clean + @echo "==> Building both normal and FIPS distributions in parallel..." + $(MAKE) dist + @if $(MAKE) dist-fips; then \ + echo "==> Both distributions built successfully:"; \ + echo " Normal: dist/$$(./build/bin/manifest id)-$$(./build/bin/manifest version).tar.gz"; \ + echo " FIPS: dist-fips/$$(./build/bin/manifest id)-$$(./build/bin/manifest version)-fips.tar.gz"; \ + else \ + echo "==> FIPS build failed, continuing with normal distribution only:"; \ + echo " Normal: dist/$$(./build/bin/manifest id)-$$(./build/bin/manifest version).tar.gz"; \ + echo " FIPS: Build failed - check Docker/credentials"; \ + fi + ## Builds and bundles the plugin for Linux only. .PHONY: dist-linux dist-linux: apply server-linux webapp bundle @@ -315,11 +420,13 @@ kill: detach .PHONY: clean clean: rm -rf bin - rm -rf dist + rm -rf dist/ + rm -rf dist-fips/ rm -rf webapp/pack ifneq ($(HAS_SERVER),) rm -fr server/coverage.txt rm -fr server/dist + rm -fr server/dist-fips endif ifneq ($(HAS_WEBAPP),) rm -fr webapp/junit.xml @@ -355,17 +462,17 @@ live-watch-webapp: apply .PHONY: deploy-to-mattermost-directory deploy-to-mattermost-directory: ./build/bin/pluginctl disable $(PLUGIN_ID) - mkdir -p $(FOCALBOARD_PLUGIN_PATH) - cp $(MANIFEST_FILE) $(FOCALBOARD_PLUGIN_PATH)/ - cp -r webapp/pack $(FOCALBOARD_PLUGIN_PATH)/ - cp -r $(ASSETS_DIR) $(FOCALBOARD_PLUGIN_PATH)/ - cp -r public $(FOCALBOARD_PLUGIN_PATH)/ - mkdir -p $(FOCALBOARD_PLUGIN_PATH)/server - cp -r server/dist $(FOCALBOARD_PLUGIN_PATH)/server/ - mkdir -p $(FOCALBOARD_PLUGIN_PATH)/webapp - cp -r webapp/dist $(FOCALBOARD_PLUGIN_PATH)/webapp/ + mkdir -p $(BOARD_PLUGIN_PATH) + cp $(MANIFEST_FILE) $(BOARD_PLUGIN_PATH)/ + cp -r webapp/pack $(BOARD_PLUGIN_PATH)/ + cp -r $(ASSETS_DIR) $(BOARD_PLUGIN_PATH)/ + cp -r public $(BOARD_PLUGIN_PATH)/ + mkdir -p $(BOARD_PLUGIN_PATH)/server + cp -r server/dist $(BOARD_PLUGIN_PATH)/server/ + mkdir -p $(BOARD_PLUGIN_PATH)/webapp + cp -r webapp/dist $(BOARD_PLUGIN_PATH)/webapp/ ./build/bin/pluginctl enable $(PLUGIN_ID) - @echo plugin built at: $(FOCALBOARD_PLUGIN_PATH) + @echo plugin built at: $(BOARD_PLUGIN_PATH) # Help documentation à la https://marmelab.com/blog/2016/02/29/auto-documented-makefile.html help: diff --git a/build/manifest/main.go b/build/manifest/main.go index d80f16f42..3cd87180e 100644 --- a/build/manifest/main.go +++ b/build/manifest/main.go @@ -67,6 +67,16 @@ func main() { panic("failed to apply manifest: " + err.Error()) } + case "dist": + if err := distManifest(manifest, "dist"); err != nil { + panic("failed to write manifest to dist directory: " + err.Error()) + } + + case "dist-fips": + if err := distManifest(manifest, "dist-fips"); err != nil { + panic("failed to write manifest to dist-fips directory: " + err.Error()) + } + default: panic("unrecognized command: " + cmd) } @@ -97,12 +107,12 @@ func findManifest() (*model.Manifest, error) { // dumpPluginId writes the plugin id from the given manifest to standard out func dumpPluginID(manifest *model.Manifest) { - fmt.Printf("%s", manifest.Id) + fmt.Printf("%s\n", manifest.Id) } // dumpPluginVersion writes the plugin version from the given manifest to standard out func dumpPluginVersion(manifest *model.Manifest) { - fmt.Printf("%s", manifest.Version) + fmt.Printf("%s\n", manifest.Version) } // applyManifest propagates the plugin_id into the server and webapp folders, as necessary @@ -127,3 +137,17 @@ func applyManifest(manifest *model.Manifest) error { return nil } + +// distManifest writes the manifest file to the specified directory +func distManifest(manifest *model.Manifest, outputDir string) error { + manifestBytes, err := json.MarshalIndent(manifest, "", " ") + if err != nil { + return err + } + + if err := os.WriteFile(fmt.Sprintf("%s/%s/plugin.json", outputDir, manifest.Id), manifestBytes, 0600); err != nil { + return errors.Wrap(err, "failed to write plugin.json") + } + + return nil +} diff --git a/server/.golangci.yml b/server/.golangci.yml index d8bc3347e..a61d68b8e 100644 --- a/server/.golangci.yml +++ b/server/.golangci.yml @@ -1,27 +1,18 @@ +version: 2 + run: timeout: 5m - modules-download-mode: readonly linters-settings: - gofmt: - simplify: true - goimports: - local-prefixes: github.com/mattermost/mattermost-starter-template - golint: - min-confidence: 0 govet: - enable-all: true + enable: + - shadow disable: - fieldalignment - misspell: - locale: US - lll: - line-length: 150 - revive: - enableAllRules: true - rules: - - name: exported - disabled: true + staticcheck: + checks: + - all + - "-QF1008" linters: disable-all: true @@ -30,39 +21,13 @@ linters: enable: - shadow enable: - - gofmt - - goimports - - ineffassign - - unparam + - errcheck - govet - - bodyclose - - durationcheck - - errorlint - - exhaustive - - copyloopvar - - gosec - - makezero + - ineffassign - staticcheck - - prealloc - - asciicheck - - dogsled - - gocritic - - godot - - err113 - - goheader - - revive - - nakedret - - gomodguard - - goprintffuncname - - gosimple - - misspell - - nolintlint - - typecheck - - unconvert - unused - - whitespace - - gocyclo - + - misspell + issues: exclude-files: - product/boards_product.go @@ -71,10 +36,12 @@ issues: - path: server/manifest.go linters: - unused - - path: server/configuration.go + - path: server/configuration.go linters: - unused - path: _test\.go linters: - bodyclose - - scopelint # https://github.com/kyoh86/scopelint/issues/4 + - linters: + - staticcheck + source: QF1008 diff --git a/server/api/api.go b/server/api/api.go index aa6a4e499..dc108be48 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -211,7 +211,7 @@ func stringResponse(w http.ResponseWriter, message string) { func jsonStringResponse(w http.ResponseWriter, code int, message string) { //nolint:unparam setResponseHeader(w, "Content-Type", "application/json") w.WriteHeader(code) - fmt.Fprint(w, message) + _, _ = fmt.Fprint(w, message) } func jsonBytesResponse(w http.ResponseWriter, code int, json []byte) { //nolint:unparam diff --git a/server/api/api_test.go b/server/api/api_test.go index 028fa0cd8..85e87e9e0 100644 --- a/server/api/api_test.go +++ b/server/api/api_test.go @@ -75,7 +75,7 @@ func TestErrorResponse(t *testing.T) { require.Equal(t, "application/json", res.Header.Get("Content-Type")) b, rErr := io.ReadAll(res.Body) require.NoError(t, rErr) - res.Body.Close() + _ = res.Body.Close() require.Contains(t, string(b), tc.ResponseBody) }) } diff --git a/server/api/archive.go b/server/api/archive.go index 73d4ff824..a632c972d 100644 --- a/server/api/archive.go +++ b/server/api/archive.go @@ -152,10 +152,10 @@ func (a *API) handleArchiveImport(w http.ResponseWriter, r *http.Request) { file, handle, err := r.FormFile(UploadFormFileKey) if err != nil { - fmt.Fprintf(w, "%v", err) + _, _ = fmt.Fprintf(w, "%v", err) return } - defer file.Close() + defer func() { _ = file.Close() }() auditRec := a.makeAuditRecord(r, "import", audit.Fail) defer a.audit.LogRecord(audit.LevelModify, auditRec) diff --git a/server/api/files.go b/server/api/files.go index ac19fadae..aecfea23b 100644 --- a/server/api/files.go +++ b/server/api/files.go @@ -175,7 +175,7 @@ func (a *API) handleServeFile(w http.ResponseWriter, r *http.Request) { a.errorResponse(w, r, err) } - defer fileReader.Close() + defer func() { _ = fileReader.Close() }() mimeType := "" var fileSize int64 @@ -393,7 +393,7 @@ func (a *API) handleUploadFile(w http.ResponseWriter, r *http.Request) { a.errorResponse(w, r, model.NewErrBadRequest(err.Error())) return } - defer file.Close() + defer func() { _ = file.Close() }() auditRec := a.makeAuditRecord(r, "uploadFile", audit.Fail) defer a.audit.LogRecord(audit.LevelModify, auditRec) diff --git a/server/api/members.go b/server/api/members.go index da30034b2..ebd163573 100644 --- a/server/api/members.go +++ b/server/api/members.go @@ -131,7 +131,7 @@ func (a *API) handleAddMember(w http.ResponseWriter, r *http.Request) { } if !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardRoles) && - !(board.Type == model.BoardTypeOpen && a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties)) { + (board.Type != model.BoardTypeOpen || !a.permissions.HasPermissionToBoard(userID, boardID, model.PermissionManageBoardProperties)) { a.errorResponse(w, r, model.NewErrPermission("access denied to modify board members")) return } diff --git a/server/app/boards.go b/server/app/boards.go index f8904a2cf..2c9704dec 100644 --- a/server/app/boards.go +++ b/server/app/boards.go @@ -431,9 +431,10 @@ func (a *App) broadcastTeamUsers(teamID, boardID string, boardType model.BoardTy } } if !isMember { - if boardType == model.BoardTypePrivate { + switch boardType { + case model.BoardTypePrivate: a.wsAdapter.BroadcastMemberDelete(teamID, boardID, user.ID) - } else if boardType == model.BoardTypeOpen { + case model.BoardTypeOpen: a.wsAdapter.BroadcastMemberChange(teamID, boardID, &model.BoardMember{UserID: user.ID, BoardID: boardID, SchemeViewer: true, Synthetic: true}) } } diff --git a/server/app/category_boards.go b/server/app/category_boards.go index 99109ec4c..a9715e2b7 100644 --- a/server/app/category_boards.go +++ b/server/app/category_boards.go @@ -222,7 +222,7 @@ func (a *App) verifyNewCategoryBoardsMatchExisting(userID, teamID, categoryID st var targetCategoryBoards *model.CategoryBoards for i := range existingCategoryBoards { - if existingCategoryBoards[i].Category.ID == categoryID { + if existingCategoryBoards[i].ID == categoryID { targetCategoryBoards = &existingCategoryBoards[i] break } diff --git a/server/app/export.go b/server/app/export.go index 79559a770..047cbaf69 100644 --- a/server/app/export.go +++ b/server/app/export.go @@ -220,7 +220,7 @@ func (a *App) writeArchiveFile(zw *zip.Writer, filename string, boardID string, ) return nil } - defer fileReader.Close() + defer func() { _ = fileReader.Close() }() _, err = io.Copy(dest, fileReader) return err diff --git a/server/assets/build-template-archive/main.go b/server/assets/build-template-archive/main.go index 3d76ca0d9..764fa7590 100644 --- a/server/assets/build-template-archive/main.go +++ b/server/assets/build-template-archive/main.go @@ -50,7 +50,7 @@ func main() { code = -1 fmt.Fprintf(os.Stderr, "error creating archive: %v\n", err) } else if cfg.verbose { - fmt.Fprintf(os.Stdout, "archive created: %s\n", cfg.out) + _, _ = fmt.Fprintf(os.Stdout, "archive created: %s\n", cfg.out) } os.Exit(code) @@ -98,7 +98,7 @@ func build(cfg appConfig) (err error) { for _, f := range files { if !f.IsDir() { if f.Name() != versionFilename && cfg.verbose { - fmt.Fprintf(os.Stdout, "skipping non-directory %s\n", f.Name()) + _, _ = fmt.Fprintf(os.Stdout, "skipping non-directory %s\n", f.Name()) } continue } @@ -144,7 +144,7 @@ func writeBoard(w *zip.Writer, boardID string, cfg appConfig) error { for _, f := range files { if f.IsDir() { if cfg.verbose { - fmt.Fprintf(os.Stdout, "skipping directory %s\n", f.Name()) + _, _ = fmt.Fprintf(os.Stdout, "skipping directory %s\n", f.Name()) } continue } @@ -166,7 +166,7 @@ func writeFile(w *zip.Writer, srcPath string, destPath string, cfg appConfig) (e if err != nil { return fmt.Errorf("error reading %s: %w", srcPath, err) } - defer inFile.Close() + defer func() { _ = inFile.Close() }() outFile, err := w.Create(destPath) if err != nil { @@ -178,7 +178,7 @@ func writeFile(w *zip.Writer, srcPath string, destPath string, cfg appConfig) (e } if cfg.verbose { - fmt.Fprintf(os.Stdout, "%s written (%d bytes)\n", destPath, size) + _, _ = fmt.Fprintf(os.Stdout, "%s written (%d bytes)\n", destPath, size) } return nil diff --git a/server/boards/boardsapp_test.go b/server/boards/boardsapp_test.go index c33d47544..e12c873b0 100644 --- a/server/boards/boardsapp_test.go +++ b/server/boards/boardsapp_test.go @@ -115,7 +115,7 @@ func TestServeHTTP(t *testing.T) { result := w.Result() assert.NotNil(result) - defer result.Body.Close() + defer func() { _ = result.Body.Close() }() bodyBytes, err := io.ReadAll(result.Body) assert.Nil(err) bodyString := string(bodyBytes) diff --git a/server/boards/boardsapp_util.go b/server/boards/boardsapp_util.go index ba22451c4..ea7aba012 100644 --- a/server/boards/boardsapp_util.go +++ b/server/boards/boardsapp_util.go @@ -58,15 +58,9 @@ func createBoardsConfig(mmconfig mm_model.Config, baseURL string, serverID strin enableTelemetry = *mmconfig.LogSettings.EnableDiagnostics } - enablePublicSharedBoards := false - if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true { - enablePublicSharedBoards = true - } + enablePublicSharedBoards := mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true - enableBoardsDeletion := false - if mmconfig.DataRetentionSettings.EnableBoardsDeletion != nil { - enableBoardsDeletion = true - } + enableBoardsDeletion := mmconfig.DataRetentionSettings.EnableBoardsDeletion != nil featureFlags := parseFeatureFlags(mmconfig.FeatureFlags.ToMap()) diff --git a/server/boards/configuration.go b/server/boards/configuration.go index 30134bdbe..dc12de111 100644 --- a/server/boards/configuration.go +++ b/server/boards/configuration.go @@ -78,10 +78,7 @@ func (b *BoardsApp) OnConfigurationChange() error { } mmconfig := b.servicesAPI.GetConfig() - enableShareBoards := false - if mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true { - enableShareBoards = true - } + enableShareBoards := mmconfig.PluginSettings.Plugins[PluginName][SharedBoardsName] == true configuration := &configuration{ EnablePublicSharedBoards: enableShareBoards, @@ -90,10 +87,7 @@ func (b *BoardsApp) OnConfigurationChange() error { b.server.Config().EnablePublicSharedBoards = enableShareBoards // handle Data Retention settings - enableBoardsDeletion := false - if mmconfig.DataRetentionSettings.EnableBoardsDeletion != nil { - enableBoardsDeletion = true - } + enableBoardsDeletion := mmconfig.DataRetentionSettings.EnableBoardsDeletion != nil b.server.Config().EnableDataRetention = enableBoardsDeletion b.server.Config().DataRetentionDays = *mmconfig.DataRetentionSettings.BoardsRetentionDays b.server.Config().TeammateNameDisplay = *mmconfig.TeamSettings.TeammateNameDisplay diff --git a/server/boards/data_retention_test.go b/server/boards/data_retention_test.go index e65fb1daf..a884293b5 100644 --- a/server/boards/data_retention_test.go +++ b/server/boards/data_retention_test.go @@ -28,14 +28,14 @@ func SetupTestHelperMockStore(t *testing.T) (*TestHelperMockStore, func()) { th := &TestHelperMockStore{} origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", "1") ctrl := gomock.NewController(t) mockStore := mockstore.NewMockStore(ctrl) tearDown := func() { defer ctrl.Finish() - os.Setenv("FOCALBOARD_UNIT_TESTING", origUnitTesting) + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", origUnitTesting) } th.Server = newTestServerMock(mockStore) diff --git a/server/client/client.go b/server/client/client.go index bd3d5d7df..e7483c075 100644 --- a/server/client/client.go +++ b/server/client/client.go @@ -765,7 +765,7 @@ func (c *Client) TeamUploadFile(teamID, boardID string, data io.Reader) (*api.Fi if _, err = io.Copy(part, data); err != nil { return nil, &Response{Error: err} } - writer.Close() + _ = writer.Close() opt := func(r *http.Request) { r.Header.Add("Content-Type", writer.FormDataContentType()) @@ -880,7 +880,7 @@ func (c *Client) ImportArchive(teamID string, data io.Reader) *Response { if _, err = io.Copy(part, data); err != nil { return &Response{Error: err} } - writer.Close() + _ = writer.Close() opt := func(r *http.Request) { r.Header.Add("Content-Type", writer.FormDataContentType()) diff --git a/server/integrationtests/board_test.go b/server/integrationtests/board_test.go index 3fa17b6e1..021c252d5 100644 --- a/server/integrationtests/board_test.go +++ b/server/integrationtests/board_test.go @@ -2065,7 +2065,7 @@ func TestDuplicateBoard(t *testing.T) { for _, categoryBoard := range userCategoryBoards { for _, boardMetadata := range categoryBoard.BoardMetadata { if boardMetadata.BoardID == duplicateBoard.ID { - duplicateBoardCategoryID = categoryBoard.Category.ID + duplicateBoardCategoryID = categoryBoard.ID } } } diff --git a/server/integrationtests/clienttestlib.go b/server/integrationtests/clienttestlib.go index 454764af4..3832d9af5 100644 --- a/server/integrationtests/clienttestlib.go +++ b/server/integrationtests/clienttestlib.go @@ -261,7 +261,7 @@ func newTestServerLocalMode() *server.Server { func SetupTestHelperWithToken(t *testing.T) *TestHelper { origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", "1") sessionToken := "TESTTOKEN" @@ -282,7 +282,7 @@ func SetupTestHelper(t *testing.T) *TestHelper { func SetupTestHelperPluginMode(t *testing.T) *TestHelper { origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", "1") th := &TestHelper{ T: t, @@ -296,7 +296,7 @@ func SetupTestHelperPluginMode(t *testing.T) *TestHelper { func SetupTestHelperLocalMode(t *testing.T) *TestHelper { origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", "1") th := &TestHelper{ T: t, @@ -310,7 +310,7 @@ func SetupTestHelperLocalMode(t *testing.T) *TestHelper { func SetupTestHelperWithLicense(t *testing.T, licenseType LicenseType) *TestHelper { origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", "1") th := &TestHelper{ T: t, @@ -341,7 +341,7 @@ func (th *TestHelper) Start() *TestHelper { time.Sleep(100 * time.Millisecond) continue } - resp.Body.Close() + _ = resp.Body.Close() // Currently returns 404 // if resp.StatusCode != http.StatusOK { @@ -375,7 +375,7 @@ func (th *TestHelper) InitBasic() *TestHelper { var ErrRegisterFail = errors.New("register failed") func (th *TestHelper) TearDown() { - os.Setenv("FOCALBOARD_UNIT_TESTING", th.origEnvUnitTesting) + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", th.origEnvUnitTesting) logger := th.Server.Logger() @@ -388,7 +388,7 @@ func (th *TestHelper) TearDown() { panic(err) } - os.RemoveAll(th.Server.Config().FilesPath) + _ = os.RemoveAll(th.Server.Config().FilesPath) if err := os.Remove(th.Server.Config().DBConfigString); err == nil { logger.Debug("Removed test database", mlog.String("file", th.Server.Config().DBConfigString)) diff --git a/server/integrationtests/compliance_test.go b/server/integrationtests/compliance_test.go index d35858388..defb19480 100644 --- a/server/integrationtests/compliance_test.go +++ b/server/integrationtests/compliance_test.go @@ -21,7 +21,7 @@ var ( ) func setupTestHelperForCompliance(t *testing.T, complianceLicense bool) (*TestHelper, Clients) { - os.Setenv("FOCALBOARD_UNIT_TESTING_COMPLIANCE", strconv.FormatBool(complianceLicense)) + _ = os.Setenv("FOCALBOARD_UNIT_TESTING_COMPLIANCE", strconv.FormatBool(complianceLicense)) th := SetupTestHelperPluginMode(t) clients := setupClients(th) diff --git a/server/integrationtests/permissions_test.go b/server/integrationtests/permissions_test.go index 320950888..499ad5f3b 100644 --- a/server/integrationtests/permissions_test.go +++ b/server/integrationtests/permissions_test.go @@ -282,19 +282,19 @@ func runTestCases(t *testing.T, ttCases []TestCase, testData TestData, clients C switch tc.method { case methodGet: response, err = reqClient.DoAPIGet(url, "") - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() case methodPost: response, err = reqClient.DoAPIPost(url, tc.body) - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() case methodPatch: response, err = reqClient.DoAPIPatch(url, tc.body) - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() case methodPut: response, err = reqClient.DoAPIPut(url, tc.body) - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() case methodDelete: response, err = reqClient.DoAPIDelete(url, tc.body) - defer response.Body.Close() + defer func() { _ = response.Body.Close() }() } require.Equal(t, tc.expectedStatusCode, response.StatusCode, tc.identifier()) diff --git a/server/integrationtests/pluginteststore.go b/server/integrationtests/pluginteststore.go index 1490b594c..506cb0483 100644 --- a/server/integrationtests/pluginteststore.go +++ b/server/integrationtests/pluginteststore.go @@ -185,8 +185,8 @@ func (s *PluginTestStore) GetUsersByTeam(teamID string, asGuestID string, showEm }, nil } - switch { - case teamID == s.testTeam.ID: + switch teamID { + case s.testTeam.ID: return []*model.User{ s.users["team-member"], s.users["viewer"], @@ -195,7 +195,7 @@ func (s *PluginTestStore) GetUsersByTeam(teamID string, asGuestID string, showEm s.users["admin"], s.users["guest"], }, nil - case teamID == s.otherTeam.ID: + case s.otherTeam.ID: return []*model.User{ s.users["team-member"], s.users["viewer"], @@ -203,7 +203,7 @@ func (s *PluginTestStore) GetUsersByTeam(teamID string, asGuestID string, showEm s.users["editor"], s.users["admin"], }, nil - case teamID == s.emptyTeam.ID: + case s.emptyTeam.ID: return []*model.User{}, nil } return nil, errTestStore @@ -275,22 +275,24 @@ func (s *PluginTestStore) SearchUserChannels(teamID, userID, query string) ([]*m } func (s *PluginTestStore) GetChannel(teamID, channel string) (*mmModel.Channel, error) { - if channel == "valid-channel-id" { + switch channel { + case "valid-channel-id": return &mmModel.Channel{ TeamId: teamID, Id: "valid-channel-id", DisplayName: "Valid Channel", Name: "valid-channel", }, nil - } else if channel == "valid-channel-id-2" { + case "valid-channel-id-2": return &mmModel.Channel{ TeamId: teamID, Id: "valid-channel-id-2", DisplayName: "Valid Channel 2", Name: "valid-channel-2", }, nil + default: + return nil, errTestStore } - return nil, errTestStore } func (s *PluginTestStore) SearchBoardsForUser(term string, field model.BoardSearchField, userID string, includePublicBoards bool) ([]*model.Board, error) { diff --git a/server/model/properties_test.go b/server/model/properties_test.go index eb8d7f880..d878f08b2 100644 --- a/server/model/properties_test.go +++ b/server/model/properties_test.go @@ -15,12 +15,13 @@ import ( type MockResolver struct{} func (r MockResolver) GetUserByID(userID string) (*User, error) { - if userID == "user_id_1" { + switch userID { + case "user_id_1": return &User{ ID: "user_id_1", Username: "username_1", }, nil - } else if userID == "user_id_2" { + case "user_id_2": return &User{ ID: "user_id_2", Username: "username_2", diff --git a/server/model/team.go b/server/model/team.go index e278dae5a..cf6a9460e 100644 --- a/server/model/team.go +++ b/server/model/team.go @@ -54,7 +54,7 @@ func TeamsFromJSON(data io.Reader) []*Team { func ValidateTeamID(teamID string, isTemplate bool) error { // Validate inputs to ensure proper file path handling // Only allow GlobalTeamID for template operations to prevent path traversal attacks - if !mm_model.IsValidId(teamID) && !(isTemplate && teamID == GlobalTeamID) { + if !mm_model.IsValidId(teamID) && (!isTemplate || teamID != GlobalTeamID) { return fmt.Errorf("invalid teamID in ValidateTeamID: %s", teamID) //nolint:err113 } return nil diff --git a/server/plugin.go b/server/plugin.go index e9bce05a6..78bb9eaa3 100644 --- a/server/plugin.go +++ b/server/plugin.go @@ -27,6 +27,7 @@ type Plugin struct { } func (p *Plugin) OnActivate() error { + //nolint:staticcheck // QF1008: prefer to keep explicit MattermostPlugin qualifier client := pluginapi.NewClient(p.MattermostPlugin.API, p.MattermostPlugin.Driver) logger, _ := mlog.NewLogger() @@ -40,6 +41,7 @@ func (p *Plugin) OnActivate() error { return err } + //nolint:staticcheck // QF1008: prefer to keep explicit MattermostPlugin qualifier adapter := newServiceAPIAdapter(p.MattermostPlugin.API, client.Store, logger) boardsApp, err := boards.NewBoardsApp(adapter, manifest) diff --git a/server/services/metrics/service.go b/server/services/metrics/service.go index b57ffdccc..5f5d7a32f 100644 --- a/server/services/metrics/service.go +++ b/server/services/metrics/service.go @@ -31,10 +31,12 @@ func NewMetricsServer(address string, metricsService *Metrics, logger mlog.Logge // Run will start the prometheus server. func (h *Service) Run() error { + //nolint:staticcheck // QF1008: prefer to keep explicit Server qualifier return errors.Wrap(h.Server.ListenAndServe(), "prometheus ListenAndServe") } // Shutdown will shutdown the prometheus server. func (h *Service) Shutdown() error { + //nolint:staticcheck // QF1008: prefer to keep explicit Server qualifier return errors.Wrap(h.Server.Close(), "prometheus Close") } diff --git a/server/services/store/generators/main.go b/server/services/store/generators/main.go index bbcc6ff1e..5d15c474b 100644 --- a/server/services/store/generators/main.go +++ b/server/services/store/generators/main.go @@ -131,7 +131,7 @@ func extractStoreMetadata() (*storeMetadata, error) { if err != nil { return nil, err } - file.Close() + _ = file.Close() f, err := parser.ParseFile(fset, "", src, parser.AllErrors|parser.ParseComments) if err != nil { return nil, err diff --git a/server/services/store/sqlstore/data_migrations.go b/server/services/store/sqlstore/data_migrations.go index 1a587a71b..3a5c17f12 100644 --- a/server/services/store/sqlstore/data_migrations.go +++ b/server/services/store/sqlstore/data_migrations.go @@ -468,7 +468,8 @@ func (s *SQLStore) getBestTeamForBoard(tx sq.BaseRunner, board *model.Board) (st teamID = commonTeams[0].(string) } else { // no common teams found. Let's try finding the best suitable team - if board.Type == "D" { + switch board.Type { + case "D": // get DM's creator and pick one of their team channel, err := (s.servicesAPI).GetChannelByID(board.ChannelID) if err != nil { @@ -491,7 +492,7 @@ func (s *SQLStore) getBestTeamForBoard(tx sq.BaseRunner, board *model.Board) (st } teamID = userTeams[channel.CreatorId][0] - } else if board.Type == "G" { + case "G": // pick the team that has the most users as members teamFrequency := map[string]int{} highestFrequencyTeam := "" @@ -533,7 +534,7 @@ func (s *SQLStore) getBoardUserTeams(tx sq.BaseRunner, board *model.Board) (map[ return nil, err } - defer rows.Close() + defer func() { _ = rows.Close() }() userTeams := map[string][]string{} @@ -727,7 +728,7 @@ func (s *SQLStore) getFocalBoardTableNames() ([]string, error) { if err != nil { return nil, fmt.Errorf("error fetching FocalBoard table names: %w", err) } - defer rows.Close() + defer func() { _ = rows.Close() }() names := make([]string, 0) @@ -826,10 +827,13 @@ func (s *SQLStore) RunDeDuplicateCategoryBoardsMigration(currentMigration int) e } } - if s.dbType == model.MysqlDBType { + switch s.dbType { + case model.MysqlDBType: return s.runMySQLDeDuplicateCategoryBoardsMigration() - } else if s.dbType == model.PostgresDBType { + case model.PostgresDBType: return s.runPostgresDeDuplicateCategoryBoardsMigration() + default: + // No specific migration needed for other database types } if mErr := s.setSystemSetting(s.db, DeDuplicateCategoryBoardTableMigrationKey, strconv.FormatBool(true)); mErr != nil { diff --git a/server/services/store/sqlstore/data_migrations_test.go b/server/services/store/sqlstore/data_migrations_test.go index 09a029ea8..0582f6e53 100644 --- a/server/services/store/sqlstore/data_migrations_test.go +++ b/server/services/store/sqlstore/data_migrations_test.go @@ -252,7 +252,7 @@ func TestCheckForMismatchedCollation(t *testing.T) { sqlCollation := "SELECT table_collation FROM information_schema.tables WHERE table_name=? and table_schema=(SELECT DATABASE())" stmtCollation, err := sqlStore.db.Prepare(sqlCollation) require.NoError(t, err) - defer stmtCollation.Close() + defer func() { _ = stmtCollation.Close() }() var collation string diff --git a/server/services/store/sqlstore/helpers_test.go b/server/services/store/sqlstore/helpers_test.go index 1ecbed584..f46b6b3a0 100644 --- a/server/services/store/sqlstore/helpers_test.go +++ b/server/services/store/sqlstore/helpers_test.go @@ -16,7 +16,7 @@ import ( func SetupTests(t *testing.T) (store.Store, func()) { origUnitTesting := os.Getenv("FOCALBOARD_UNIT_TESTING") - os.Setenv("FOCALBOARD_UNIT_TESTING", "1") + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", "1") dbType, connectionString, err := PrepareNewTestDatabase() require.NoError(t, err) @@ -46,7 +46,7 @@ func SetupTests(t *testing.T) (store.Store, func()) { if err = os.Remove(connectionString); err == nil { logger.Debug("Removed test database", mlog.String("file", connectionString)) } - os.Setenv("FOCALBOARD_UNIT_TESTING", origUnitTesting) + _ = os.Setenv("FOCALBOARD_UNIT_TESTING", origUnitTesting) } return store, tearDown diff --git a/server/services/store/sqlstore/migrate.go b/server/services/store/sqlstore/migrate.go index ada332e44..f93027bb9 100644 --- a/server/services/store/sqlstore/migrate.go +++ b/server/services/store/sqlstore/migrate.go @@ -121,7 +121,9 @@ func (s *SQLStore) Migrate() error { defer func() { s.logger.Debug("Closing migrations connection") - db.Close() + if err = db.Close(); err != nil { + s.logger.Error("Error closing migrations connection", mlog.Err(err)) + } }() } @@ -206,7 +208,9 @@ func (s *SQLStore) Migrate() error { } defer func() { s.logger.Debug("Closing migration engine") - engine.Close() + if err := engine.Close(); err != nil { + s.logger.Error("Error closing migration engine", mlog.Err(err)) + } }() return s.runMigrationSequence(engine, driver) diff --git a/server/services/store/sqlstore/migrationstests/boards_migrator_test.go b/server/services/store/sqlstore/migrationstests/boards_migrator_test.go index 65731343e..e75d781a5 100644 --- a/server/services/store/sqlstore/migrationstests/boards_migrator_test.go +++ b/server/services/store/sqlstore/migrationstests/boards_migrator_test.go @@ -80,7 +80,7 @@ func (bm *BoardsMigrator) runMattermostMigrations() error { if err != nil { return err } - defer engine.Close() + defer func() { _ = engine.Close() }() return engine.ApplyAll() } diff --git a/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go b/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go index 0f6dfc948..24d284590 100644 --- a/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go +++ b/server/services/store/sqlstore/migrationstests/deleted_membership_boards_migration_test.go @@ -26,21 +26,25 @@ func TestDeletedMembershipBoardsMigration(t *testing.T) { Team_ID string }{} - th.f.DB().Get(&boardGroupChannel, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + err := th.f.DB().Get(&boardGroupChannel, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.NoError(t, err) require.Equal(t, "user-one", boardGroupChannel.Created_By) require.Equal(t, "team-one", boardGroupChannel.Team_ID) - th.f.DB().Get(&boardDirectMessage, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + err = th.f.DB().Get(&boardDirectMessage, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.NoError(t, err) require.Equal(t, "user-one", boardDirectMessage.Created_By) require.Equal(t, "team-one", boardDirectMessage.Team_ID) th.f.RunInterceptor(18) - th.f.DB().Get(&boardGroupChannel, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + err = th.f.DB().Get(&boardGroupChannel, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.NoError(t, err) require.Equal(t, "user-one", boardGroupChannel.Created_By) require.Equal(t, "team-three", boardGroupChannel.Team_ID) - th.f.DB().Get(&boardDirectMessage, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + err = th.f.DB().Get(&boardDirectMessage, "SELECT created_by, team_id FROM focalboard_boards WHERE id = 'board-group-channel'") + require.NoError(t, err) require.Equal(t, "user-one", boardDirectMessage.Created_By) require.Equal(t, "team-three", boardDirectMessage.Team_ID) }) diff --git a/server/services/store/sqlstore/migrationstests/migrate_34_test.go b/server/services/store/sqlstore/migrationstests/migrate_34_test.go index b4a173960..653c03010 100644 --- a/server/services/store/sqlstore/migrationstests/migrate_34_test.go +++ b/server/services/store/sqlstore/migrationstests/migrate_34_test.go @@ -20,12 +20,14 @@ func Test34DropDeleteAtColumnMySQLPostgres(t *testing.T) { if th.IsMySQL() { var count int query := "SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'" - th.f.DB().Get(&count, query) + err := th.f.DB().Get(&count, query) + require.NoError(t, err) require.Equal(t, 0, count) } else if th.IsPostgres() { var count int query := "select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'" - th.f.DB().Get(&count, query) + err := th.f.DB().Get(&count, query) + require.NoError(t, err) require.Equal(t, 0, count) } }) @@ -48,12 +50,14 @@ func Test34DropDeleteAtColumnMySQLPostgres(t *testing.T) { if th.IsMySQL() { var count int query := "SELECT COUNT(column_name) FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name = 'focalboard_category_boards' AND column_name = 'delete_at'" - th.f.DB().Get(&count, query) + err := th.f.DB().Get(&count, query) + require.NoError(t, err) require.Equal(t, 0, count) } else if th.IsPostgres() { var count int query := "select count(*) from information_schema.columns where table_name = 'focalboard_category_boards' and column_name = 'delete_at'" - th.f.DB().Get(&count, query) + err := th.f.DB().Get(&count, query) + require.NoError(t, err) require.Equal(t, 0, count) } }) diff --git a/server/services/store/sqlstore/migrationstests/migration36_test.go b/server/services/store/sqlstore/migrationstests/migration36_test.go index 0214d4dd3..5de104a5e 100644 --- a/server/services/store/sqlstore/migrationstests/migration36_test.go +++ b/server/services/store/sqlstore/migrationstests/migration36_test.go @@ -28,7 +28,8 @@ func Test36AddUniqueConstraintToCategoryBoards(t *testing.T) { "WHERE constraint_name = 'unique_user_category_board' " + "AND constraint_type = 'UNIQUE' " + "AND table_name = 'focalboard_category_boards'" - th.f.DB().Get(&count, query) + err := th.f.DB().Get(&count, query) + require.NoError(t, err) require.Equal(t, 1, count) }) @@ -46,9 +47,11 @@ func Test36AddUniqueConstraintToCategoryBoards(t *testing.T) { th.f.MigrateToStep(35) if th.IsMySQL() { - th.f.DB().Exec("alter table focalboard_category_boards add constraint unique_user_category_board UNIQUE(user_id, board_id);") + _, err := th.f.DB().Exec("alter table focalboard_category_boards add constraint unique_user_category_board UNIQUE(user_id, board_id);") + require.NoError(t, err) } else if th.IsPostgres() { - th.f.DB().Exec("ALTER TABLE focalboard_category_boards ADD CONSTRAINT unique_user_category_board UNIQUE(user_id, board_id);") + _, err := th.f.DB().Exec("ALTER TABLE focalboard_category_boards ADD CONSTRAINT unique_user_category_board UNIQUE(user_id, board_id);") + require.NoError(t, err) } th.f.MigrateToStep(36) @@ -66,7 +69,8 @@ func Test36AddUniqueConstraintToCategoryBoards(t *testing.T) { "AND constraint_name = 'unique_user_category_board' " + "AND constraint_type = 'UNIQUE' " + "AND table_name = 'focalboard_category_boards'" - th.f.DB().Get(&count, query) + err := th.f.DB().Get(&count, query) + require.NoError(t, err) require.Equal(t, 1, count) }) } diff --git a/server/services/store/sqlstore/migrationstests/migration_18_test.go b/server/services/store/sqlstore/migrationstests/migration_18_test.go index 79437f00f..4ef0d18d3 100644 --- a/server/services/store/sqlstore/migrationstests/migration_18_test.go +++ b/server/services/store/sqlstore/migrationstests/migration_18_test.go @@ -97,7 +97,7 @@ func Test18AddTeamsAndBoardsSQLMigration(t *testing.T) { var fields map[string]interface{} // Make sure a valid JSON object - json.Unmarshal([]byte(view.Fields), &fields) + _ = json.Unmarshal([]byte(view.Fields), &fields) require.NotNil(t, fields["columnCalculations"]) require.NotEmpty(t, fields["columnCalculations"]) diff --git a/server/services/store/sqlstore/util.go b/server/services/store/sqlstore/util.go index 2eba473e7..51aa63c75 100644 --- a/server/services/store/sqlstore/util.go +++ b/server/services/store/sqlstore/util.go @@ -80,7 +80,7 @@ func PrepareNewTestDatabase() (dbType string, connectionString string, err error if err != nil { return "", "", fmt.Errorf("cannot connect to %s database: %w", dbType, err) } - defer sqlDB.Close() + defer func() { _ = sqlDB.Close() }() err = sqlDB.Ping() if err != nil { diff --git a/server/services/telemetry/telemetry_test.go b/server/services/telemetry/telemetry_test.go index 7fa0952c3..900c0e275 100644 --- a/server/services/telemetry/telemetry_test.go +++ b/server/services/telemetry/telemetry_test.go @@ -52,8 +52,7 @@ func mockServer() (chan []byte, *httptest.Server) { func TestTelemetry(t *testing.T) { receiveChan, server := mockServer() - os.Setenv("RUDDER_KEY", "mock-test-rudder-key") - os.Setenv("RUDDER_DATAPLANE_URL", server.URL) + _ = os.Setenv("RUDDER_DATAPLANE_URL", server.URL) checkMockRudderServer := func(t *testing.T) { // check mock rudder server got diff --git a/server/services/webhook/webhook.go b/server/services/webhook/webhook.go index e5c655bcb..6dc44c1ac 100644 --- a/server/services/webhook/webhook.go +++ b/server/services/webhook/webhook.go @@ -28,7 +28,7 @@ func (wh *Client) NotifyUpdate(block *model.Block) { for _, url := range wh.config.WebhookUpdate { resp, _ := http.Post(url, "application/json", bytes.NewBuffer(json)) //nolint:gosec _, _ = io.ReadAll(resp.Body) - resp.Body.Close() + _ = resp.Body.Close() wh.logger.Debug("webhook.NotifyUpdate", mlog.String("url", url)) } diff --git a/server/web/webserver.go b/server/web/webserver.go index db4ee56ad..1717c4df6 100644 --- a/server/web/webserver.go +++ b/server/web/webserver.go @@ -79,6 +79,7 @@ func NewServer(rootPath string, serverRoot string, port int, ssl, localOnly bool } func (ws *Server) Router() *mux.Router { + //nolint:staticcheck // QF1008: prefer to keep explicit Server qualifier return ws.Server.Handler.(*mux.Router) } diff --git a/server/web/webserver_test.go b/server/web/webserver_test.go index bc4e2edb9..523e5c3c0 100644 --- a/server/web/webserver_test.go +++ b/server/web/webserver_test.go @@ -93,8 +93,10 @@ func Test_NewServer(t *testing.T) { require.Equal(t, test.logger, ws.logger, "logger pointer does not match") if test.localOnly == true { + //nolint:staticcheck // QF1008: prefer to keep explicit Server qualifier require.Equal(t, test.expectedServerAddr, ws.Server.Addr, "localhost address not as matching!") } else { + //nolint:staticcheck // QF1008: prefer to keep explicit Server qualifier require.Equal(t, test.expectedServerAddr, ws.Server.Addr, "server address not matching!") } }) diff --git a/server/ws/server.go b/server/ws/server.go index 6b224d65d..6211a1c70 100644 --- a/server/ws/server.go +++ b/server/ws/server.go @@ -121,7 +121,7 @@ func (ws *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { // Remove session from listeners ws.removeListener(wsSession) - wsSession.conn.Close() + _ = wsSession.conn.Close() }() // Simple message handling loop @@ -426,7 +426,7 @@ func (ws *Server) authenticateListener(wsSession *websocketSession, token string // Authenticate session userID := ws.getUserIDForToken(token) if userID == "" { - wsSession.conn.Close() + _ = wsSession.conn.Close() return } @@ -536,7 +536,7 @@ func (ws *Server) BroadcastBlockChange(teamID string, block *model.Block) { err := listener.WriteJSON(message) if err != nil { ws.logger.Error("broadcast error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -559,7 +559,7 @@ func (ws *Server) BroadcastCategoryChange(category model.Category) { if err := listener.WriteJSON(message); err != nil { ws.logger.Error("broadcast category change error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -581,7 +581,7 @@ func (ws *Server) BroadcastCategoryReorder(teamID, userID string, categoryOrder if err := listener.WriteJSON(message); err != nil { ws.logger.Error("broadcast category order change error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -605,7 +605,7 @@ func (ws *Server) BroadcastCategoryBoardsReorder(teamID, userID, categoryID stri if err := listener.WriteJSON(message); err != nil { ws.logger.Error("broadcast category boards order change error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -628,7 +628,7 @@ func (ws *Server) BroadcastCategoryBoardChange(teamID, userID string, boardCateg if err := listener.WriteJSON(message); err != nil { ws.logger.Error("broadcast category change error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -652,7 +652,7 @@ func (ws *Server) BroadcastConfigChange(clientConfig model.ClientConfig) { err := listener.WriteJSON(message) if err != nil { ws.logger.Error("broadcast error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -681,7 +681,7 @@ func (ws *Server) BroadcastBoardChange(teamID string, board *model.Board) { err := listener.WriteJSON(message) if err != nil { ws.logger.Error("broadcast error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -721,7 +721,7 @@ func (ws *Server) BroadcastMemberChange(teamID, boardID string, member *model.Bo err := listener.WriteJSON(message) if err != nil { ws.logger.Error("broadcast error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } } @@ -753,7 +753,7 @@ func (ws *Server) BroadcastMemberDelete(teamID, boardID, userID string) { err := listener.WriteJSON(message) if err != nil { ws.logger.Error("broadcast error", mlog.Err(err)) - listener.conn.Close() + _ = listener.conn.Close() } } }