diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 44604d5a693..532e79c766c 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -85,6 +85,7 @@ func dashboardGuardianResponse(err error) response.Response { // 401: unauthorisedError // 403: forbiddenError // 404: notFoundError +// 406: notAcceptableError // 500: internalServerError // //nolint:gocyclo @@ -99,14 +100,10 @@ func (hs *HTTPServer) GetDashboard(c *contextmodel.ReqContext) response.Response return rsp } - // V2 values should be read from the k8s API + // v2 is not supported in /api if strings.HasPrefix(dash.APIVersion, "v2") { - root := hs.Cfg.AppSubURL - if !strings.HasSuffix(root, "/") { - root += "/" - } - url := fmt.Sprintf("%sapis/dashboard.grafana.app/%s/namespaces/%s/dashboards/%s", root, dash.APIVersion, hs.namespacer(c.OrgID), dash.UID) - return response.Redirect(url) + url := fmt.Sprintf("/apis/dashboard.grafana.app/%s/namespaces/%s/dashboards/%s", dash.APIVersion, hs.namespacer(c.SignedInUser.GetOrgID()), dash.UID) + return response.Error(http.StatusNotAcceptable, "dashboard api version not supported, use "+url+" instead", nil) } var ( diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index e1039a1a3e5..3ed18a073e8 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "strconv" "testing" "time" @@ -694,6 +695,46 @@ func TestDashboardAPIEndpoint(t *testing.T) { assert.Equal(t, false, dash.Meta.Provisioned) }, mockSQLStore) }) + + t.Run("v2 dashboards should not be returned in api", func(t *testing.T) { + mockSQLStore := dbtest.NewFakeDB() + dashboardService := dashboards.NewFakeDashboardService(t) + + dataValue, err := simplejson.NewJson([]byte(`{"id": 1, "apiVersion": "v2"}`)) + require.NoError(t, err) + qResult := &dashboards.Dashboard{ + ID: 1, + UID: "dash", + OrgID: 1, + APIVersion: "v2", + Data: dataValue, + } + dashboardService.On("GetDashboard", mock.Anything, mock.AnythingOfType("*dashboards.GetDashboardQuery")).Return(qResult, nil) + guardian.MockDashboardGuardian(&guardian.FakeDashboardGuardian{CanViewValue: true}) + + loggedInUserScenarioWithRole(t, "When calling GET on", "GET", "/api/dashboards/uid/dash", "/api/dashboards/uid/:uid", org.RoleEditor, func(sc *scenarioContext) { + hs := &HTTPServer{ + Cfg: setting.NewCfg(), + LibraryPanelService: &mockLibraryPanelService{}, + LibraryElementService: &libraryelementsfake.LibraryElementService{}, + SQLStore: mockSQLStore, + AccessControl: accesscontrolmock.New(), + DashboardService: dashboardService, + Features: featuremgmt.WithFeatures(), + starService: startest.NewStarServiceFake(), + tracer: tracing.InitializeTracerForTest(), + dashboardProvisioningService: mockDashboardProvisioningService{}, + folderService: foldertest.NewFakeService(), + log: log.New("test"), + namespacer: func(orgID int64) string { return strconv.FormatInt(orgID, 10) }, + } + hs.callGetDashboard(sc) + + assert.Equal(t, http.StatusNotAcceptable, sc.resp.Code) + result := sc.ToJSON() + assert.Equal(t, "dashboard api version not supported, use /apis/dashboard.grafana.app/v2/namespaces/1/dashboards/dash instead", result.Get("message").MustString()) + }, mockSQLStore) + }) } func TestDashboardVersionsAPIEndpoint(t *testing.T) { diff --git a/pkg/api/swagger_responses.go b/pkg/api/swagger_responses.go index 4b529ca4112..2e41234cf10 100644 --- a/pkg/api/swagger_responses.go +++ b/pkg/api/swagger_responses.go @@ -52,6 +52,11 @@ type NotFoundError GenericError // swagger:response badRequestError type BadRequestError GenericError +// NotAcceptableError is returned when the server cannot produce a response matching the accepted formats. +// +// swagger:response notAcceptableError +type NotAcceptableError GenericError + // ConflictError // // swagger:response conflictError diff --git a/pkg/tests/apis/dashboard/dashboards_test.go b/pkg/tests/apis/dashboard/dashboards_test.go index 317945da7f3..acc43f31aef 100644 --- a/pkg/tests/apis/dashboard/dashboards_test.go +++ b/pkg/tests/apis/dashboard/dashboards_test.go @@ -334,10 +334,10 @@ func TestIntegrationLegacySupport(t *testing.T) { require.Equal(t, 200, rsp.Response.StatusCode) require.Equal(t, "v1alpha1", rsp.Result.Meta.APIVersion) - // V2 should send a redirect + // V2 should send a not acceptable rsp = apis.DoRequest(helper, apis.RequestParams{ User: helper.Org1.Admin, Path: "/api/dashboards/uid/test-v2", }, &dtos.DashboardFullWithMeta{}) - require.Equal(t, 302, rsp.Response.StatusCode) // redirect + require.Equal(t, 406, rsp.Response.StatusCode) // not acceptable } diff --git a/public/api-merged.json b/public/api-merged.json index 4f216cfbc95..130059510ae 100644 --- a/public/api-merged.json +++ b/public/api-merged.json @@ -3356,6 +3356,9 @@ "404": { "$ref": "#/responses/notFoundError" }, + "406": { + "$ref": "#/responses/notAcceptableError" + }, "500": { "$ref": "#/responses/internalServerError" } @@ -24440,6 +24443,12 @@ "type": "object" } }, + "notAcceptableError": { + "description": "NotAcceptableError is returned when the server cannot produce a response matching the accepted formats.", + "schema": { + "$ref": "#/definitions/ErrorResponseBody" + } + }, "notFoundError": { "description": "NotFoundError is returned when the requested resource was not found.", "schema": { diff --git a/public/openapi3.json b/public/openapi3.json index 5f3d84d0826..d791ee2bb10 100644 --- a/public/openapi3.json +++ b/public/openapi3.json @@ -1641,6 +1641,16 @@ }, "description": "(empty)" }, + "notAcceptableError": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ErrorResponseBody" + } + } + }, + "description": "NotAcceptableError is returned when the server cannot produce a response matching the accepted formats." + }, "notFoundError": { "content": { "application/json": { @@ -16997,6 +17007,9 @@ "404": { "$ref": "#/components/responses/notFoundError" }, + "406": { + "$ref": "#/components/responses/notAcceptableError" + }, "500": { "$ref": "#/components/responses/internalServerError" }