mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Unistore: Get Folder By ID (#99131)
* Unistore: Get Folder By ID Signed-off-by: Maicon Costa <maiconscosta@gmail.com> --------- Signed-off-by: Maicon Costa <maiconscosta@gmail.com> Co-authored-by: Stephanie Hingtgen <stephanie.hingtgen@grafana.com>
This commit is contained in:
@@ -20,7 +20,7 @@ import (
|
|||||||
dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
dashboardv0alpha1 "github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||||
dashboardsvc "github.com/grafana/grafana/pkg/services/dashboards/service"
|
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
"github.com/grafana/grafana/pkg/util/errhttp"
|
"github.com/grafana/grafana/pkg/util/errhttp"
|
||||||
)
|
)
|
||||||
@@ -339,7 +339,7 @@ func (s *SearchHandler) DoSearch(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedResults, err := dashboardsvc.ParseResults(result, searchRequest.Offset)
|
parsedResults, err := dashboardsearch.ParseResults(result, searchRequest.Offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errhttp.Write(ctx, err, w)
|
errhttp.Write(ctx, err, w)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package service
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/binary"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -21,8 +20,6 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
"k8s.io/apimachinery/pkg/selection"
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
|
|
||||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
|
||||||
|
|
||||||
claims "github.com/grafana/authlib/types"
|
claims "github.com/grafana/authlib/types"
|
||||||
|
|
||||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||||
@@ -39,6 +36,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||||
|
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
|
||||||
"github.com/grafana/grafana/pkg/services/datasources"
|
"github.com/grafana/grafana/pkg/services/datasources"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
@@ -71,16 +69,6 @@ var (
|
|||||||
tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/dashboards/service")
|
tracer = otel.Tracer("github.com/grafana/grafana/pkg/services/dashboards/service")
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
excludedFields = map[string]string{
|
|
||||||
resource.SEARCH_FIELD_EXPLAIN: "",
|
|
||||||
resource.SEARCH_FIELD_SCORE: "",
|
|
||||||
resource.SEARCH_FIELD_TITLE: "",
|
|
||||||
resource.SEARCH_FIELD_FOLDER: "",
|
|
||||||
resource.SEARCH_FIELD_TAGS: "",
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
type DashboardServiceImpl struct {
|
type DashboardServiceImpl struct {
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
log log.Logger
|
log log.Logger
|
||||||
@@ -1661,7 +1649,7 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8sRaw(ctx context.Contex
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseResults(res, 0)
|
return dashboardsearch.ParseResults(res, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
type dashboardProvisioningWithUID struct {
|
type dashboardProvisioningWithUID struct {
|
||||||
@@ -1765,103 +1753,6 @@ func (dr *DashboardServiceImpl) searchDashboardsThroughK8s(ctx context.Context,
|
|||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseResults(result *resource.ResourceSearchResponse, offset int64) (*v0alpha1.SearchResults, error) {
|
|
||||||
if result == nil {
|
|
||||||
return nil, nil
|
|
||||||
} else if result.Error != nil {
|
|
||||||
return nil, fmt.Errorf("%d error searching: %s: %s", result.Error.Code, result.Error.Message, result.Error.Details)
|
|
||||||
} else if result.Results == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
titleIDX := 0
|
|
||||||
folderIDX := 1
|
|
||||||
tagsIDX := -1
|
|
||||||
scoreIDX := 0
|
|
||||||
explainIDX := 0
|
|
||||||
|
|
||||||
for i, v := range result.Results.Columns {
|
|
||||||
switch v.Name {
|
|
||||||
case resource.SEARCH_FIELD_EXPLAIN:
|
|
||||||
explainIDX = i
|
|
||||||
case resource.SEARCH_FIELD_SCORE:
|
|
||||||
scoreIDX = i
|
|
||||||
case resource.SEARCH_FIELD_TITLE:
|
|
||||||
titleIDX = i
|
|
||||||
case resource.SEARCH_FIELD_FOLDER:
|
|
||||||
folderIDX = i
|
|
||||||
case resource.SEARCH_FIELD_TAGS:
|
|
||||||
tagsIDX = i
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sr := &v0alpha1.SearchResults{
|
|
||||||
Offset: offset,
|
|
||||||
TotalHits: result.TotalHits,
|
|
||||||
QueryCost: result.QueryCost,
|
|
||||||
MaxScore: result.MaxScore,
|
|
||||||
Hits: make([]v0alpha1.DashboardHit, len(result.Results.Rows)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, row := range result.Results.Rows {
|
|
||||||
fields := &common.Unstructured{}
|
|
||||||
for colIndex, col := range result.Results.Columns {
|
|
||||||
if _, ok := excludedFields[col.Name]; !ok {
|
|
||||||
val, err := resource.DecodeCell(col, colIndex, row.Cells[colIndex])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Some of the dashboard fields come in as int32, but we need to convert them to int64 or else fields.Set() will panic
|
|
||||||
int32Val, ok := val.(int32)
|
|
||||||
if ok {
|
|
||||||
val = int64(int32Val)
|
|
||||||
}
|
|
||||||
fields.Set(col.Name, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
hit := &v0alpha1.DashboardHit{
|
|
||||||
Resource: row.Key.Resource, // folders | dashboards
|
|
||||||
Name: row.Key.Name, // The Grafana UID
|
|
||||||
Title: string(row.Cells[titleIDX]),
|
|
||||||
Folder: string(row.Cells[folderIDX]),
|
|
||||||
Field: fields,
|
|
||||||
}
|
|
||||||
if tagsIDX > 0 && row.Cells[tagsIDX] != nil {
|
|
||||||
_ = json.Unmarshal(row.Cells[tagsIDX], &hit.Tags)
|
|
||||||
}
|
|
||||||
if explainIDX > 0 && row.Cells[explainIDX] != nil {
|
|
||||||
_ = json.Unmarshal(row.Cells[explainIDX], &hit.Explain)
|
|
||||||
}
|
|
||||||
if scoreIDX > 0 && row.Cells[scoreIDX] != nil {
|
|
||||||
_, _ = binary.Decode(row.Cells[scoreIDX], binary.BigEndian, &hit.Score)
|
|
||||||
}
|
|
||||||
|
|
||||||
sr.Hits[i] = *hit
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add facet results
|
|
||||||
if result.Facet != nil {
|
|
||||||
sr.Facets = make(map[string]v0alpha1.FacetResult)
|
|
||||||
for k, v := range result.Facet {
|
|
||||||
sr.Facets[k] = v0alpha1.FacetResult{
|
|
||||||
Field: v.Field,
|
|
||||||
Total: v.Total,
|
|
||||||
Missing: v.Missing,
|
|
||||||
Terms: make([]v0alpha1.TermFacet, len(v.Terms)),
|
|
||||||
}
|
|
||||||
for j, t := range v.Terms {
|
|
||||||
sr.Facets[k].Terms[j] = v0alpha1.TermFacet{
|
|
||||||
Term: t.Term,
|
|
||||||
Count: t.Count,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return sr, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (dr *DashboardServiceImpl) UnstructuredToLegacyDashboard(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) {
|
func (dr *DashboardServiceImpl) UnstructuredToLegacyDashboard(ctx context.Context, item *unstructured.Unstructured, orgID int64) (*dashboards.Dashboard, error) {
|
||||||
spec, ok := item.Object["spec"].(map[string]any)
|
spec, ok := item.Object["spec"].(map[string]any)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/search"
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@@ -1818,51 +1817,3 @@ func TestToUID(t *testing.T) {
|
|||||||
assert.Equal(t, "", result)
|
assert.Equal(t, "", result)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// regression test - parsing int32 values from search results was causing a panic
|
|
||||||
func TestParseResults(t *testing.T) {
|
|
||||||
resSearchResp := &resource.ResourceSearchResponse{
|
|
||||||
Results: &resource.ResourceTable{
|
|
||||||
Columns: []*resource.ResourceTableColumnDefinition{
|
|
||||||
{
|
|
||||||
Name: "title",
|
|
||||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "folder",
|
|
||||||
Type: resource.ResourceTableColumnDefinition_STRING,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
|
|
||||||
Type: resource.ResourceTableColumnDefinition_INT64,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: search.DASHBOARD_LINK_COUNT,
|
|
||||||
Type: resource.ResourceTableColumnDefinition_INT32,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Rows: []*resource.ResourceTableRow{
|
|
||||||
{
|
|
||||||
Key: &resource.ResourceKey{
|
|
||||||
Name: "uid",
|
|
||||||
Resource: "dashboard",
|
|
||||||
},
|
|
||||||
Cells: [][]byte{
|
|
||||||
[]byte("Dashboard 1"),
|
|
||||||
[]byte("folder1"),
|
|
||||||
[]byte("100"),
|
|
||||||
[]byte("25"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TotalHits: 1,
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := ParseResults(resSearchResp, 0)
|
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
hitFields := res.Hits[0].Field.Object
|
|
||||||
require.Equal(t, int64(100), hitFields[search.DASHBOARD_ERRORS_LAST_1_DAYS])
|
|
||||||
require.Equal(t, int64(25), hitFields[search.DASHBOARD_LINK_COUNT])
|
|
||||||
}
|
|
||||||
|
|||||||
119
pkg/services/dashboards/service/search/search.go
Normal file
119
pkg/services/dashboards/service/search/search.go
Normal file
@@ -0,0 +1,119 @@
|
|||||||
|
package dashboardsearch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
excludedFields = map[string]string{
|
||||||
|
resource.SEARCH_FIELD_EXPLAIN: "",
|
||||||
|
resource.SEARCH_FIELD_SCORE: "",
|
||||||
|
resource.SEARCH_FIELD_TITLE: "",
|
||||||
|
resource.SEARCH_FIELD_FOLDER: "",
|
||||||
|
resource.SEARCH_FIELD_TAGS: "",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func ParseResults(result *resource.ResourceSearchResponse, offset int64) (*v0alpha1.SearchResults, error) {
|
||||||
|
if result == nil {
|
||||||
|
return nil, nil
|
||||||
|
} else if result.Error != nil {
|
||||||
|
return nil, fmt.Errorf("%d error searching: %s: %s", result.Error.Code, result.Error.Message, result.Error.Details)
|
||||||
|
} else if result.Results == nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
titleIDX := 0
|
||||||
|
folderIDX := 1
|
||||||
|
tagsIDX := -1
|
||||||
|
scoreIDX := 0
|
||||||
|
explainIDX := 0
|
||||||
|
|
||||||
|
for i, v := range result.Results.Columns {
|
||||||
|
switch v.Name {
|
||||||
|
case resource.SEARCH_FIELD_EXPLAIN:
|
||||||
|
explainIDX = i
|
||||||
|
case resource.SEARCH_FIELD_SCORE:
|
||||||
|
scoreIDX = i
|
||||||
|
case resource.SEARCH_FIELD_TITLE:
|
||||||
|
titleIDX = i
|
||||||
|
case resource.SEARCH_FIELD_FOLDER:
|
||||||
|
folderIDX = i
|
||||||
|
case resource.SEARCH_FIELD_TAGS:
|
||||||
|
tagsIDX = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sr := &v0alpha1.SearchResults{
|
||||||
|
Offset: offset,
|
||||||
|
TotalHits: result.TotalHits,
|
||||||
|
QueryCost: result.QueryCost,
|
||||||
|
MaxScore: result.MaxScore,
|
||||||
|
Hits: make([]v0alpha1.DashboardHit, len(result.Results.Rows)),
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, row := range result.Results.Rows {
|
||||||
|
fields := &common.Unstructured{}
|
||||||
|
for colIndex, col := range result.Results.Columns {
|
||||||
|
if _, ok := excludedFields[col.Name]; !ok {
|
||||||
|
val, err := resource.DecodeCell(col, colIndex, row.Cells[colIndex])
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Some of the dashboard fields come in as int32, but we need to convert them to int64 or else fields.Set() will panic
|
||||||
|
int32Val, ok := val.(int32)
|
||||||
|
if ok {
|
||||||
|
val = int64(int32Val)
|
||||||
|
}
|
||||||
|
fields.Set(col.Name, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hit := &v0alpha1.DashboardHit{
|
||||||
|
Resource: row.Key.Resource, // folders | dashboards
|
||||||
|
Name: row.Key.Name, // The Grafana UID
|
||||||
|
Title: string(row.Cells[titleIDX]),
|
||||||
|
Folder: string(row.Cells[folderIDX]),
|
||||||
|
Field: fields,
|
||||||
|
}
|
||||||
|
if tagsIDX > 0 && row.Cells[tagsIDX] != nil {
|
||||||
|
_ = json.Unmarshal(row.Cells[tagsIDX], &hit.Tags)
|
||||||
|
}
|
||||||
|
if explainIDX > 0 && row.Cells[explainIDX] != nil {
|
||||||
|
_ = json.Unmarshal(row.Cells[explainIDX], &hit.Explain)
|
||||||
|
}
|
||||||
|
if scoreIDX > 0 && row.Cells[scoreIDX] != nil {
|
||||||
|
_, _ = binary.Decode(row.Cells[scoreIDX], binary.BigEndian, &hit.Score)
|
||||||
|
}
|
||||||
|
|
||||||
|
sr.Hits[i] = *hit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add facet results
|
||||||
|
if result.Facet != nil {
|
||||||
|
sr.Facets = make(map[string]v0alpha1.FacetResult)
|
||||||
|
for k, v := range result.Facet {
|
||||||
|
sr.Facets[k] = v0alpha1.FacetResult{
|
||||||
|
Field: v.Field,
|
||||||
|
Total: v.Total,
|
||||||
|
Missing: v.Missing,
|
||||||
|
Terms: make([]v0alpha1.TermFacet, len(v.Terms)),
|
||||||
|
}
|
||||||
|
for j, t := range v.Terms {
|
||||||
|
sr.Facets[k].Terms[j] = v0alpha1.TermFacet{
|
||||||
|
Term: t.Term,
|
||||||
|
Count: t.Count,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sr, nil
|
||||||
|
}
|
||||||
53
pkg/services/dashboards/service/search/search_test.go
Normal file
53
pkg/services/dashboards/service/search/search_test.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package dashboardsearch
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/search"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
// regression test - parsing int32 values from search results was causing a panic
|
||||||
|
func TestParseResults(t *testing.T) {
|
||||||
|
resSearchResp := &resource.ResourceSearchResponse{
|
||||||
|
Results: &resource.ResourceTable{
|
||||||
|
Columns: []*resource.ResourceTableColumnDefinition{
|
||||||
|
{
|
||||||
|
Name: "title",
|
||||||
|
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "folder",
|
||||||
|
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: search.DASHBOARD_ERRORS_LAST_1_DAYS,
|
||||||
|
Type: resource.ResourceTableColumnDefinition_INT64,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: search.DASHBOARD_LINK_COUNT,
|
||||||
|
Type: resource.ResourceTableColumnDefinition_INT32,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Rows: []*resource.ResourceTableRow{
|
||||||
|
{
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "uid",
|
||||||
|
Resource: "dashboard",
|
||||||
|
},
|
||||||
|
Cells: [][]byte{
|
||||||
|
[]byte("Dashboard 1"),
|
||||||
|
[]byte("folder1"),
|
||||||
|
[]byte("100"),
|
||||||
|
[]byte("25"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TotalHits: 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ParseResults(resSearchResp, 0)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
@@ -38,6 +38,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/supportbundles"
|
"github.com/grafana/grafana/pkg/services/supportbundles"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -76,21 +77,11 @@ func ProvideService(
|
|||||||
r prometheus.Registerer,
|
r prometheus.Registerer,
|
||||||
tracer tracing.Tracer,
|
tracer tracing.Tracer,
|
||||||
) *Service {
|
) *Service {
|
||||||
k8sHandler := &foldk8sHandler{
|
|
||||||
gvr: v0alpha1.FolderResourceInfo.GroupVersionResource(),
|
|
||||||
namespacer: request.GetNamespaceMapper(cfg),
|
|
||||||
cfg: cfg,
|
|
||||||
restConfigProvider: apiserver.GetRestConfig,
|
|
||||||
}
|
|
||||||
|
|
||||||
unifiedStore := ProvideUnifiedStore(k8sHandler)
|
|
||||||
|
|
||||||
srv := &Service{
|
srv := &Service{
|
||||||
log: slog.Default().With("logger", "folder-service"),
|
log: slog.Default().With("logger", "folder-service"),
|
||||||
dashboardStore: dashboardStore,
|
dashboardStore: dashboardStore,
|
||||||
dashboardFolderStore: folderStore,
|
dashboardFolderStore: folderStore,
|
||||||
store: store,
|
store: store,
|
||||||
unifiedStore: unifiedStore,
|
|
||||||
features: features,
|
features: features,
|
||||||
accessControl: ac,
|
accessControl: ac,
|
||||||
bus: bus,
|
bus: bus,
|
||||||
@@ -98,7 +89,6 @@ func ProvideService(
|
|||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
metrics: newFoldersMetrics(r),
|
metrics: newFoldersMetrics(r),
|
||||||
tracer: tracer,
|
tracer: tracer,
|
||||||
k8sclient: k8sHandler,
|
|
||||||
}
|
}
|
||||||
srv.DBMigration(db)
|
srv.DBMigration(db)
|
||||||
|
|
||||||
@@ -106,6 +96,22 @@ func ProvideService(
|
|||||||
|
|
||||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
|
ac.RegisterScopeAttributeResolver(dashboards.NewFolderIDScopeResolver(folderStore, srv))
|
||||||
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
|
ac.RegisterScopeAttributeResolver(dashboards.NewFolderUIDScopeResolver(srv))
|
||||||
|
|
||||||
|
if features.IsEnabledGlobally(featuremgmt.FlagKubernetesFoldersServiceV2) {
|
||||||
|
k8sHandler := &foldk8sHandler{
|
||||||
|
gvr: v0alpha1.FolderResourceInfo.GroupVersionResource(),
|
||||||
|
namespacer: request.GetNamespaceMapper(cfg),
|
||||||
|
cfg: cfg,
|
||||||
|
restConfigProvider: apiserver.GetRestConfig,
|
||||||
|
recourceClientProvider: unified.GetResourceClient,
|
||||||
|
}
|
||||||
|
|
||||||
|
unifiedStore := ProvideUnifiedStore(k8sHandler)
|
||||||
|
|
||||||
|
srv.unifiedStore = unifiedStore
|
||||||
|
srv.k8sclient = k8sHandler
|
||||||
|
}
|
||||||
|
|
||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package folderimpl
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -9,21 +10,26 @@ import (
|
|||||||
"go.opentelemetry.io/otel/trace"
|
"go.opentelemetry.io/otel/trace"
|
||||||
"golang.org/x/exp/slices"
|
"golang.org/x/exp/slices"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/selection"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
clientrest "k8s.io/client-go/rest"
|
clientrest "k8s.io/client-go/rest"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
|
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/events"
|
"github.com/grafana/grafana/pkg/events"
|
||||||
"github.com/grafana/grafana/pkg/infra/metrics"
|
"github.com/grafana/grafana/pkg/infra/metrics"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
"github.com/grafana/grafana/pkg/services/dashboards/dashboardaccess"
|
||||||
|
dashboardsearch "github.com/grafana/grafana/pkg/services/dashboards/service/search"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/services/guardian"
|
"github.com/grafana/grafana/pkg/services/guardian"
|
||||||
"github.com/grafana/grafana/pkg/services/store/entity"
|
"github.com/grafana/grafana/pkg/services/store/entity"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -31,15 +37,17 @@ import (
|
|||||||
type folderK8sHandler interface {
|
type folderK8sHandler interface {
|
||||||
getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
|
getClient(ctx context.Context, orgID int64) (dynamic.ResourceInterface, bool)
|
||||||
getNamespace(orgID int64) string
|
getNamespace(orgID int64) string
|
||||||
|
getSearcher(ctx context.Context) resource.ResourceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
var _ folderK8sHandler = (*foldk8sHandler)(nil)
|
var _ folderK8sHandler = (*foldk8sHandler)(nil)
|
||||||
|
|
||||||
type foldk8sHandler struct {
|
type foldk8sHandler struct {
|
||||||
cfg *setting.Cfg
|
cfg *setting.Cfg
|
||||||
namespacer request.NamespaceMapper
|
namespacer request.NamespaceMapper
|
||||||
gvr schema.GroupVersionResource
|
gvr schema.GroupVersionResource
|
||||||
restConfigProvider func(ctx context.Context) *clientrest.Config
|
restConfigProvider func(ctx context.Context) *clientrest.Config
|
||||||
|
recourceClientProvider func(ctx context.Context) resource.ResourceClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) getFoldersFromApiServer(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
|
func (s *Service) getFoldersFromApiServer(ctx context.Context, q folder.GetFoldersQuery) ([]*folder.Folder, error) {
|
||||||
@@ -93,6 +101,8 @@ func (s *Service) getFromApiServer(ctx context.Context, q *folder.GetFolderQuery
|
|||||||
return folder.SharedWithMeFolder.WithURL(), nil
|
return folder.SharedWithMeFolder.WithURL(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ctx = identity.WithRequester(ctx, q.SignedInUser)
|
||||||
|
|
||||||
var dashFolder *folder.Folder
|
var dashFolder *folder.Folder
|
||||||
var err error
|
var err error
|
||||||
switch {
|
switch {
|
||||||
@@ -102,13 +112,17 @@ func (s *Service) getFromApiServer(ctx context.Context, q *folder.GetFolderQuery
|
|||||||
}
|
}
|
||||||
dashFolder, err = s.unifiedStore.Get(ctx, *q)
|
dashFolder, err = s.unifiedStore.Get(ctx, *q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, toFolderError(err)
|
||||||
}
|
}
|
||||||
// nolint:staticcheck
|
// nolint:staticcheck
|
||||||
case q.ID != nil:
|
case q.ID != nil:
|
||||||
// not implemented
|
dashFolder, err = s.getFolderByIDFromApiServer(ctx, *q.ID, q.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, toFolderError(err)
|
||||||
|
}
|
||||||
case q.Title != nil:
|
case q.Title != nil:
|
||||||
// not implemented
|
// not implemented
|
||||||
|
return nil, folder.ErrBadRequest.Errorf("not implemented")
|
||||||
default:
|
default:
|
||||||
return nil, folder.ErrBadRequest.Errorf("either on of UID, ID, Title fields must be present")
|
return nil, folder.ErrBadRequest.Errorf("either on of UID, ID, Title fields must be present")
|
||||||
}
|
}
|
||||||
@@ -155,6 +169,61 @@ func (s *Service) getFromApiServer(ctx context.Context, q *folder.GetFolderQuery
|
|||||||
return f, err
|
return f, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Service) getFolderByIDFromApiServer(ctx context.Context, id int64, orgID int64) (*folder.Folder, error) {
|
||||||
|
if id == 0 {
|
||||||
|
return &folder.GeneralFolder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
folderkey := &resource.ResourceKey{
|
||||||
|
Namespace: s.k8sclient.getNamespace(orgID),
|
||||||
|
Group: v0alpha1.FolderResourceInfo.GroupVersionResource().Group,
|
||||||
|
Resource: v0alpha1.FolderResourceInfo.GroupVersionResource().Resource,
|
||||||
|
}
|
||||||
|
|
||||||
|
request := &resource.ResourceSearchRequest{
|
||||||
|
Options: &resource.ListOptions{
|
||||||
|
Key: folderkey,
|
||||||
|
Fields: []*resource.Requirement{},
|
||||||
|
Labels: []*resource.Requirement{
|
||||||
|
{
|
||||||
|
Key: utils.LabelKeyDeprecatedInternalID, // nolint:staticcheck
|
||||||
|
Operator: string(selection.In),
|
||||||
|
Values: []string{fmt.Sprintf("%d", id)},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Limit: 100000}
|
||||||
|
|
||||||
|
client := s.k8sclient.getSearcher(ctx)
|
||||||
|
|
||||||
|
res, err := client.Search(ctx, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
hits, err := dashboardsearch.ParseResults(res, 0)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(hits.Hits) == 0 {
|
||||||
|
return nil, dashboards.ErrFolderNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
uid := hits.Hits[0].Name
|
||||||
|
user, err := identity.GetRequester(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := s.Get(ctx, &folder.GetFolderQuery{UID: &uid, SignedInUser: user, OrgID: orgID})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return f, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Service) getChildrenFromApiServer(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.Folder, error) {
|
func (s *Service) getChildrenFromApiServer(ctx context.Context, q *folder.GetChildrenQuery) ([]*folder.Folder, error) {
|
||||||
defer func(t time.Time) {
|
defer func(t time.Time) {
|
||||||
parent := q.UID
|
parent := q.UID
|
||||||
@@ -697,3 +766,7 @@ func (fk8s *foldk8sHandler) getClient(ctx context.Context, orgID int64) (dynamic
|
|||||||
func (fk8s *foldk8sHandler) getNamespace(orgID int64) string {
|
func (fk8s *foldk8sHandler) getNamespace(orgID int64) string {
|
||||||
return fk8s.namespacer(orgID)
|
return fk8s.namespacer(orgID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (fk8s *foldk8sHandler) getSearcher(ctx context.Context) resource.ResourceClient {
|
||||||
|
return fk8s.recourceClientProvider(ctx)
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,9 +11,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"google.golang.org/grpc"
|
||||||
clientrest "k8s.io/client-go/rest"
|
clientrest "k8s.io/client-go/rest"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||||
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/bus"
|
"github.com/grafana/grafana/pkg/bus"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
@@ -31,6 +33,7 @@ import (
|
|||||||
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
)
|
)
|
||||||
|
|
||||||
type rcp struct {
|
type rcp struct {
|
||||||
@@ -54,6 +57,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
|||||||
unifiedStorageFolder.Kind = "folder"
|
unifiedStorageFolder.Kind = "folder"
|
||||||
|
|
||||||
fooFolder := &folder.Folder{
|
fooFolder := &folder.Folder{
|
||||||
|
ID: 123,
|
||||||
Title: "Foo Folder",
|
Title: "Foo Folder",
|
||||||
OrgID: orgID,
|
OrgID: orgID,
|
||||||
UID: "foo",
|
UID: "foo",
|
||||||
@@ -163,11 +167,16 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
|||||||
Host: folderApiServerMock.URL,
|
Host: folderApiServerMock.URL,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
f := func(ctx context.Context) resource.ResourceClient {
|
||||||
|
return resourceClientMock{}
|
||||||
|
}
|
||||||
|
|
||||||
k8sHandler := &foldk8sHandler{
|
k8sHandler := &foldk8sHandler{
|
||||||
gvr: v0alpha1.FolderResourceInfo.GroupVersionResource(),
|
gvr: v0alpha1.FolderResourceInfo.GroupVersionResource(),
|
||||||
namespacer: request.GetNamespaceMapper(cfg),
|
namespacer: request.GetNamespaceMapper(cfg),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
restConfigProvider: restCfgProvider.GetRestConfig,
|
restConfigProvider: restCfgProvider.GetRestConfig,
|
||||||
|
recourceClientProvider: f,
|
||||||
}
|
}
|
||||||
|
|
||||||
unifiedStore := ProvideUnifiedStore(k8sHandler)
|
unifiedStore := ProvideUnifiedStore(k8sHandler)
|
||||||
@@ -205,6 +214,7 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
|||||||
registry: make(map[string]folder.RegistryService),
|
registry: make(map[string]folder.RegistryService),
|
||||||
metrics: newFoldersMetrics(nil),
|
metrics: newFoldersMetrics(nil),
|
||||||
tracer: tracing.InitializeTracerForTest(),
|
tracer: tracing.InitializeTracerForTest(),
|
||||||
|
k8sclient: k8sHandler,
|
||||||
}
|
}
|
||||||
|
|
||||||
require.NoError(t, folderService.RegisterService(alertingStore))
|
require.NoError(t, folderService.RegisterService(alertingStore))
|
||||||
@@ -402,6 +412,32 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("When get folder by ID should return folder", func(t *testing.T) {
|
||||||
|
id := int64(123)
|
||||||
|
query := &folder.GetFolderQuery{
|
||||||
|
ID: &id,
|
||||||
|
OrgID: 1,
|
||||||
|
SignedInUser: usr,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := folderService.Get(context.Background(), query)
|
||||||
|
require.Equal(t, fooFolder, actual)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("When get folder by non existing ID should return not found error", func(t *testing.T) {
|
||||||
|
id := int64(111111)
|
||||||
|
query := &folder.GetFolderQuery{
|
||||||
|
ID: &id,
|
||||||
|
OrgID: 1,
|
||||||
|
SignedInUser: usr,
|
||||||
|
}
|
||||||
|
|
||||||
|
actual, err := folderService.Get(context.Background(), query)
|
||||||
|
require.Nil(t, actual)
|
||||||
|
require.ErrorIs(t, err, dashboards.ErrFolderNotFound)
|
||||||
|
})
|
||||||
|
|
||||||
// TODO!!
|
// TODO!!
|
||||||
/*
|
/*
|
||||||
t.Run("When get folder by title should return folder", func(t *testing.T) {
|
t.Run("When get folder by title should return folder", func(t *testing.T) {
|
||||||
@@ -434,3 +470,90 @@ func TestIntegrationFolderServiceViaUnifiedStorage(t *testing.T) {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type resourceClientMock struct{}
|
||||||
|
|
||||||
|
func (r resourceClientMock) Read(ctx context.Context, in *resource.ReadRequest, opts ...grpc.CallOption) (*resource.ReadResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) Create(ctx context.Context, in *resource.CreateRequest, opts ...grpc.CallOption) (*resource.CreateResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) Update(ctx context.Context, in *resource.UpdateRequest, opts ...grpc.CallOption) (*resource.UpdateResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) Delete(ctx context.Context, in *resource.DeleteRequest, opts ...grpc.CallOption) (*resource.DeleteResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) Restore(ctx context.Context, in *resource.RestoreRequest, opts ...grpc.CallOption) (*resource.RestoreResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) List(ctx context.Context, in *resource.ListRequest, opts ...grpc.CallOption) (*resource.ListResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) Watch(ctx context.Context, in *resource.WatchRequest, opts ...grpc.CallOption) (resource.ResourceStore_WatchClient, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) Search(ctx context.Context, in *resource.ResourceSearchRequest, opts ...grpc.CallOption) (*resource.ResourceSearchResponse, error) {
|
||||||
|
if len(in.Options.Labels) > 0 &&
|
||||||
|
in.Options.Labels[0].Key == utils.LabelKeyDeprecatedInternalID &&
|
||||||
|
in.Options.Labels[0].Operator == "in" &&
|
||||||
|
len(in.Options.Labels[0].Values) > 0 &&
|
||||||
|
in.Options.Labels[0].Values[0] == "123" {
|
||||||
|
return &resource.ResourceSearchResponse{
|
||||||
|
Results: &resource.ResourceTable{
|
||||||
|
Columns: []*resource.ResourceTableColumnDefinition{
|
||||||
|
{
|
||||||
|
Name: "_id",
|
||||||
|
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "title",
|
||||||
|
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "folder",
|
||||||
|
Type: resource.ResourceTableColumnDefinition_STRING,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Rows: []*resource.ResourceTableRow{
|
||||||
|
{
|
||||||
|
Key: &resource.ResourceKey{
|
||||||
|
Name: "foo",
|
||||||
|
Resource: "folders",
|
||||||
|
},
|
||||||
|
Cells: [][]byte{
|
||||||
|
[]byte("123"),
|
||||||
|
[]byte("folder1"),
|
||||||
|
[]byte(""),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
TotalHits: 1,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// not found
|
||||||
|
return &resource.ResourceSearchResponse{
|
||||||
|
Results: &resource.ResourceTable{},
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) GetStats(ctx context.Context, in *resource.ResourceStatsRequest, opts ...grpc.CallOption) (*resource.ResourceStatsResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) CountRepositoryObjects(ctx context.Context, in *resource.CountRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.CountRepositoryObjectsResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) ListRepositoryObjects(ctx context.Context, in *resource.ListRepositoryObjectsRequest, opts ...grpc.CallOption) (*resource.ListRepositoryObjectsResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) PutBlob(ctx context.Context, in *resource.PutBlobRequest, opts ...grpc.CallOption) (*resource.PutBlobResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) GetBlob(ctx context.Context, in *resource.GetBlobRequest, opts ...grpc.CallOption) (*resource.GetBlobResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
func (r resourceClientMock) IsHealthy(ctx context.Context, in *resource.HealthCheckRequest, opts ...grpc.CallOption) (*resource.HealthCheckResponse, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||||
k8sUser "k8s.io/apiserver/pkg/authentication/user"
|
k8sUser "k8s.io/apiserver/pkg/authentication/user"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
|
internalfolders "github.com/grafana/grafana/pkg/registry/apis/folders"
|
||||||
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
)
|
)
|
||||||
@@ -157,10 +159,11 @@ func (ss *FolderUnifiedStoreImpl) Get(ctx context.Context, q folder.GetFolderQue
|
|||||||
}
|
}
|
||||||
|
|
||||||
out, err := client.Get(newCtx, *q.UID, v1.GetOptions{})
|
out, err := client.Get(newCtx, *q.UID, v1.GetOptions{})
|
||||||
if err != nil {
|
if err != nil && !apierrors.IsNotFound(err) {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else if err != nil || out == nil {
|
||||||
|
return nil, dashboards.ErrFolderNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
dashFolder, _ := internalfolders.UnstructuredToLegacyFolder(*out, q.SignedInUser.GetOrgID())
|
dashFolder, _ := internalfolders.UnstructuredToLegacyFolder(*out, q.SignedInUser.GetOrgID())
|
||||||
return dashFolder, nil
|
return dashFolder, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ import (
|
|||||||
|
|
||||||
const resourceStoreAudience = "resourceStore"
|
const resourceStoreAudience = "resourceStore"
|
||||||
|
|
||||||
|
var (
|
||||||
|
// internal provider of the package level resource client
|
||||||
|
pkgResourceClient resource.ResourceClient
|
||||||
|
ready = make(chan struct{})
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetResourceClient(ctx context.Context) resource.ResourceClient {
|
||||||
|
<-ready
|
||||||
|
return pkgResourceClient
|
||||||
|
}
|
||||||
|
|
||||||
// This adds a UnifiedStorage client into the wire dependency tree
|
// This adds a UnifiedStorage client into the wire dependency tree
|
||||||
func ProvideUnifiedStorageClient(
|
func ProvideUnifiedStorageClient(
|
||||||
cfg *setting.Cfg,
|
cfg *setting.Cfg,
|
||||||
@@ -53,6 +64,13 @@ func ProvideUnifiedStorageClient(
|
|||||||
legacysql.NewDatabaseProvider(db),
|
legacysql.NewDatabaseProvider(db),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// only set the package level restConfig once
|
||||||
|
if pkgResourceClient == nil {
|
||||||
|
pkgResourceClient = client
|
||||||
|
close(ready)
|
||||||
|
}
|
||||||
|
|
||||||
return client, err
|
return client, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package federated
|
package federatedtests
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -27,6 +27,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
"github.com/grafana/grafana/pkg/services/tag/tagimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/storage/legacysql"
|
"github.com/grafana/grafana/pkg/storage/legacysql"
|
||||||
|
"github.com/grafana/grafana/pkg/storage/unified/federated"
|
||||||
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
"github.com/grafana/grafana/pkg/storage/unified/resource"
|
||||||
"github.com/grafana/grafana/pkg/tests/testsuite"
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
||||||
)
|
)
|
||||||
@@ -102,7 +103,7 @@ func TestDirectSQLStats(t *testing.T) {
|
|||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
store := &LegacyStatsGetter{
|
store := &federated.LegacyStatsGetter{
|
||||||
SQL: legacysql.NewDatabaseProvider(db),
|
SQL: legacysql.NewDatabaseProvider(db),
|
||||||
}
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user