diff --git a/api/v4/source/cloud.yaml b/api/v4/source/cloud.yaml index 14985775e3b..ef47b59c6f1 100644 --- a/api/v4/source/cloud.yaml +++ b/api/v4/source/cloud.yaml @@ -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: diff --git a/server/channels/api4/cloud.go b/server/channels/api4/cloud.go index bdd4d1d3191..5e1eac99757 100644 --- a/server/channels/api4/cloud.go +++ b/server/channels/api4/cloud.go @@ -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) { diff --git a/server/channels/api4/cloud_test.go b/server/channels/api4/cloud_test.go index eeb9c4aceff..e3844bf01dd 100644 --- a/server/channels/api4/cloud_test.go +++ b/server/channels/api4/cloud_test.go @@ -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"]) + }) +} diff --git a/server/i18n/en.json b/server/i18n/en.json index e5b8faa1397..1074740a23d 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -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" diff --git a/webapp/channels/src/packages/mattermost-redux/src/actions/general.ts b/webapp/channels/src/packages/mattermost-redux/src/actions/general.ts index 9350fd7d118..5367e0f7537 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/actions/general.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/actions/general.ts @@ -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'}; diff --git a/webapp/platform/client/src/client4.ts b/webapp/platform/client/src/client4.ts index de125af8711..efe713a2d29 100644 --- a/webapp/platform/client/src/client4.ts +++ b/webapp/platform/client/src/client4.ts @@ -4242,7 +4242,7 @@ export default class Client4 { }; cwsAvailabilityCheck = () => { - return this.doFetchWithResponse( + return this.doFetch<{status: string}>( `${this.getCloudRoute()}/check-cws-connection`, {method: 'get'}, );