mirror of
https://github.com/mattermost/mattermost.git
synced 2026-02-03 20:40:00 -05:00
[MM-67021] Fix 500 errors on check-cws-connection in non-Cloud environments (#34786)
* Fix 500 errors on check-cws-connection in non-Cloud environments The check-cws-connection endpoint was returning 500 errors in self-hosted enterprise environments because: 1. The client only checked BuildEnterpriseReady before making the request, which is true for all enterprise builds 2. The server handler didn't check for a Cloud license before attempting to connect to CWS 3. The CWS URL is not configured in non-Cloud environments, causing the connection check to fail This fix: - Server: Add IsCloud() license check to match other cloud endpoints, returning 403 instead of 500 for non-Cloud licenses - Client: Add Cloud license check to skip the request entirely in non-Cloud environments * Add unit tests for check-cws-connection license check * Return JSON status from check-cws-connection endpoint Change the check-cws-connection endpoint to return 200 with a JSON body containing status (available/unavailable) instead of using HTTP error codes. This allows the endpoint to be used for air-gap detection on self-hosted instances, not just Cloud deployments. * i18n --------- Co-authored-by: Mattermost Build <build@mattermost.com>
This commit is contained in:
parent
b74b5fe83f
commit
70a50edcf2
6 changed files with 99 additions and 12 deletions
|
|
@ -360,6 +360,36 @@
|
|||
$ref: "#/components/responses/Forbidden"
|
||||
"501":
|
||||
$ref: "#/components/responses/NotImplemented"
|
||||
/api/v4/cloud/check-cws-connection:
|
||||
get:
|
||||
tags:
|
||||
- cloud
|
||||
summary: Check CWS connection
|
||||
description: >
|
||||
Checks whether the Customer Web Server (CWS) is reachable from this instance.
|
||||
Used to detect if the deployment is air-gapped.
|
||||
|
||||
##### Permissions
|
||||
|
||||
No permissions required.
|
||||
|
||||
__Minimum server version__: 5.28
|
||||
__Note:__ This is intended for internal use and is subject to change.
|
||||
operationId: CheckCWSConnection
|
||||
responses:
|
||||
"200":
|
||||
description: CWS connection status returned successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
description: Connection status - "available" if CWS is reachable, "unavailable" if not
|
||||
enum:
|
||||
- available
|
||||
- unavailable
|
||||
/api/v4/cloud/webhook:
|
||||
post:
|
||||
tags:
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ func (api *API) InitCloud() {
|
|||
// GET /api/v4/cloud/installation
|
||||
api.BaseRoutes.Cloud.Handle("/installation", api.APISessionRequired(getInstallation)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/cloud/cws-health-check
|
||||
// GET /api/v4/cloud/check-cws-connection
|
||||
api.BaseRoutes.Cloud.Handle("/check-cws-connection", api.APIHandler(handleCheckCWSConnection)).Methods(http.MethodGet)
|
||||
|
||||
// GET /api/v4/cloud/preview/modal_data
|
||||
|
|
@ -605,12 +605,16 @@ func handleCheckCWSConnection(c *Context, w http.ResponseWriter, r *http.Request
|
|||
return
|
||||
}
|
||||
|
||||
status := "available"
|
||||
if err := c.App.Cloud().CheckCWSConnection(c.AppContext.Session().UserId); err != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCWSHealthCheck", "api.server.cws.health_check.app_error", nil, "CWS Server is not available.", http.StatusInternalServerError)
|
||||
return
|
||||
status = "unavailable"
|
||||
}
|
||||
|
||||
ReturnStatusOK(w)
|
||||
response := map[string]string{"status": status}
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
c.Err = model.NewAppError("Api4.handleCheckCWSConnection", "api.cloud.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func getPreviewModalData(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
|
|
|
|||
|
|
@ -5,10 +5,12 @@ package api4
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
|
|
@ -430,3 +432,57 @@ func TestGetCloudProducts(t *testing.T) {
|
|||
require.Equal(t, returnedProducts[2].CrossSellsTo, "prod_test2")
|
||||
})
|
||||
}
|
||||
|
||||
func TestCheckCWSConnection(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
|
||||
t.Run("returns available when CWS is reachable", func(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
cloud.Mock.On("CheckCWSConnection", mock.Anything).Return(nil)
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
r, err := th.Client.DoAPIGet(context.Background(), "/cloud/check-cws-connection", "")
|
||||
require.NoError(t, err)
|
||||
defer closeBody(r)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode)
|
||||
|
||||
var response map[string]string
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&response))
|
||||
assert.Equal(t, "available", response["status"])
|
||||
})
|
||||
|
||||
t.Run("returns unavailable when CWS is not reachable", func(t *testing.T) {
|
||||
mainHelper.Parallel(t)
|
||||
th := Setup(t).InitBasic(t)
|
||||
|
||||
th.App.Srv().SetLicense(model.NewTestLicense())
|
||||
|
||||
cloud := mocks.CloudInterface{}
|
||||
cloud.Mock.On("CheckCWSConnection", mock.Anything).Return(errors.New("connection failed"))
|
||||
|
||||
cloudImpl := th.App.Srv().Cloud
|
||||
defer func() {
|
||||
th.App.Srv().Cloud = cloudImpl
|
||||
}()
|
||||
th.App.Srv().Cloud = &cloud
|
||||
|
||||
r, err := th.Client.DoAPIGet(context.Background(), "/cloud/check-cws-connection", "")
|
||||
require.NoError(t, err)
|
||||
defer closeBody(r)
|
||||
require.Equal(t, http.StatusOK, r.StatusCode)
|
||||
|
||||
var response map[string]string
|
||||
require.NoError(t, json.NewDecoder(r.Body).Decode(&response))
|
||||
assert.Equal(t, "unavailable", response["status"])
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3146,10 +3146,6 @@
|
|||
"id": "api.server.cws.disabled",
|
||||
"translation": "Interactions with the Mattermost Customer Portal have been disabled by the system admin."
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.health_check.app_error",
|
||||
"translation": "CWS Server is not available."
|
||||
},
|
||||
{
|
||||
"id": "api.server.cws.needs_enterprise_edition",
|
||||
"translation": "Service only available in Mattermost Enterprise edition"
|
||||
|
|
|
|||
|
|
@ -121,9 +121,10 @@ export function checkCWSAvailability(): ActionFuncAsync {
|
|||
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_REQUEST});
|
||||
|
||||
try {
|
||||
await Client4.cwsAvailabilityCheck();
|
||||
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_SUCCESS, data: 'available'});
|
||||
return {data: 'available'};
|
||||
const response = await Client4.cwsAvailabilityCheck();
|
||||
const status = response.status;
|
||||
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_SUCCESS, data: status});
|
||||
return {data: status};
|
||||
} catch (error) {
|
||||
dispatch({type: GeneralTypes.CWS_AVAILABILITY_CHECK_FAILURE});
|
||||
return {data: 'unavailable'};
|
||||
|
|
|
|||
|
|
@ -4242,7 +4242,7 @@ export default class Client4 {
|
|||
};
|
||||
|
||||
cwsAvailabilityCheck = () => {
|
||||
return this.doFetchWithResponse(
|
||||
return this.doFetch<{status: string}>(
|
||||
`${this.getCloudRoute()}/check-cws-connection`,
|
||||
{method: 'get'},
|
||||
);
|
||||
|
|
|
|||
Loading…
Reference in a new issue