mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: Namespace parsing updates (default + stack-id) (#76310)
Co-authored-by: Todd Treece <360020+toddtreece@users.noreply.github.com>
This commit is contained in:
parent
be7fe761a3
commit
2a527aa33b
@ -8,6 +8,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
type namespaceMapper = func(orgId int64) string
|
||||
@ -19,6 +20,13 @@ func orgNamespaceMapper(orgId int64) string {
|
||||
return fmt.Sprintf("org-%d", orgId)
|
||||
}
|
||||
|
||||
func getNamespaceMapper(cfg *setting.Cfg) namespaceMapper {
|
||||
if cfg.StackID != "" {
|
||||
return func(orgId int64) string { return "stack-" + cfg.StackID }
|
||||
}
|
||||
return orgNamespaceMapper
|
||||
}
|
||||
|
||||
func convertToK8sResource(v *playlist.PlaylistDTO, namespacer namespaceMapper) *Playlist {
|
||||
spec := Spec{
|
||||
Title: v.Name,
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func TestPlaylistConversion(t *testing.T) {
|
||||
@ -62,3 +63,36 @@ func TestPlaylistConversion(t *testing.T) {
|
||||
}
|
||||
}`, string(out))
|
||||
}
|
||||
|
||||
func TestNamespaceMapper(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cfg string
|
||||
orgId int64
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "default namespace",
|
||||
orgId: 1,
|
||||
expected: "default",
|
||||
},
|
||||
{
|
||||
name: "with org",
|
||||
orgId: 123,
|
||||
expected: "org-123",
|
||||
},
|
||||
{
|
||||
name: "with stackId",
|
||||
cfg: "abc",
|
||||
orgId: 123, // ignored
|
||||
expected: "stack-abc",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
mapper := getNamespaceMapper(&setting.Cfg{StackID: tt.cfg})
|
||||
require.Equal(t, tt.expected, mapper(tt.orgId))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -22,13 +22,8 @@ var (
|
||||
)
|
||||
|
||||
type legacyStorage struct {
|
||||
service playlist.Service
|
||||
}
|
||||
|
||||
func newLegacyStorage(s playlist.Service) *legacyStorage {
|
||||
return &legacyStorage{
|
||||
service: s,
|
||||
}
|
||||
service playlist.Service
|
||||
namespacer namespaceMapper
|
||||
}
|
||||
|
||||
func (s *legacyStorage) New() runtime.Object {
|
||||
@ -56,9 +51,9 @@ func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Objec
|
||||
func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
// TODO: handle fetching all available orgs when no namespace is specified
|
||||
// To test: kubectl get playlists --all-namespaces
|
||||
orgId, ok := grafanarequest.OrgIDFrom(ctx)
|
||||
if !ok {
|
||||
orgId = 1 // TODO: default org ID 1 for now
|
||||
info, err := grafanarequest.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
limit := 100
|
||||
@ -66,7 +61,7 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
|
||||
limit = int(options.Limit)
|
||||
}
|
||||
res, err := s.service.Search(ctx, &playlist.GetPlaylistsQuery{
|
||||
OrgId: orgId,
|
||||
OrgId: info.OrgID,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
@ -82,12 +77,12 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
|
||||
for _, v := range res {
|
||||
p, err := s.service.Get(ctx, &playlist.GetPlaylistByUidQuery{
|
||||
UID: v.UID,
|
||||
OrgId: orgId, // required
|
||||
OrgId: info.OrgID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list.Items = append(list.Items, *convertToK8sResource(p, orgNamespaceMapper))
|
||||
list.Items = append(list.Items, *convertToK8sResource(p, s.namespacer))
|
||||
}
|
||||
if len(list.Items) == limit {
|
||||
list.Continue = "<more>" // TODO?
|
||||
@ -96,14 +91,14 @@ func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListO
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
orgId, ok := grafanarequest.OrgIDFrom(ctx)
|
||||
if !ok {
|
||||
orgId = 1 // TODO: default org ID 1 for now
|
||||
info, err := grafanarequest.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dto, err := s.service.Get(ctx, &playlist.GetPlaylistByUidQuery{
|
||||
UID: name,
|
||||
OrgId: orgId,
|
||||
OrgId: info.OrgID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -112,5 +107,5 @@ func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.Ge
|
||||
return nil, fmt.Errorf("not found?")
|
||||
}
|
||||
|
||||
return convertToK8sResource(dto, orgNamespaceMapper), nil
|
||||
return convertToK8sResource(dto, s.namespacer), nil
|
||||
}
|
||||
|
@ -13,18 +13,24 @@ import (
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
|
||||
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var _ grafanaapiserver.APIGroupBuilder = (*PlaylistAPIBuilder)(nil)
|
||||
|
||||
// This is used just so wire has something unique to return
|
||||
type PlaylistAPIBuilder struct {
|
||||
service playlist.Service
|
||||
service playlist.Service
|
||||
namespacer namespaceMapper
|
||||
}
|
||||
|
||||
func RegisterAPIService(p playlist.Service, apiregistration grafanaapiserver.APIRegistrar) *PlaylistAPIBuilder {
|
||||
func RegisterAPIService(p playlist.Service,
|
||||
apiregistration grafanaapiserver.APIRegistrar,
|
||||
cfg *setting.Cfg,
|
||||
) *PlaylistAPIBuilder {
|
||||
builder := &PlaylistAPIBuilder{
|
||||
service: p,
|
||||
service: p,
|
||||
namespacer: getNamespaceMapper(cfg),
|
||||
}
|
||||
apiregistration.RegisterAPI(builder)
|
||||
return builder
|
||||
@ -50,7 +56,10 @@ func (b *PlaylistAPIBuilder) GetAPIGroupInfo(
|
||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
legacyStore := newLegacyStorage(b.service)
|
||||
legacyStore := &legacyStorage{
|
||||
service: b.service,
|
||||
namespacer: b.namespacer,
|
||||
}
|
||||
storage["playlists"] = legacyStore
|
||||
|
||||
// enable dual writes if a RESTOptionsGetter is provided
|
||||
|
@ -20,7 +20,10 @@ type OrgIDAuthorizer struct {
|
||||
}
|
||||
|
||||
func ProvideOrgIDAuthorizer(orgService org.Service) *OrgIDAuthorizer {
|
||||
return &OrgIDAuthorizer{log: log.New("grafana-apiserver.authorizer.orgid"), org: orgService}
|
||||
return &OrgIDAuthorizer{
|
||||
log: log.New("grafana-apiserver.authorizer.orgid"),
|
||||
org: orgService,
|
||||
}
|
||||
}
|
||||
|
||||
func (auth OrgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
@ -29,11 +32,26 @@ func (auth OrgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
|
||||
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
|
||||
}
|
||||
|
||||
orgID, ok := grafanarequest.ParseOrgID(a.GetNamespace())
|
||||
if !ok {
|
||||
info, err := grafanarequest.ParseNamespace(a.GetNamespace())
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, fmt.Sprintf("error reading namespace: %v", err), nil
|
||||
}
|
||||
|
||||
// No opinion when the namespace is arbitrary
|
||||
if info.OrgID == -1 {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
if info.StackID != "" {
|
||||
return authorizer.DecisionDeny, "using a stack namespace requires deployment with a fixed stack id", nil
|
||||
}
|
||||
|
||||
// Quick check that the same org is used
|
||||
if signedInUser.OrgID == info.OrgID {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
// Check if the user has access to the specified org
|
||||
query := org.GetUserOrgListQuery{UserID: signedInUser.UserID}
|
||||
result, err := auth.org.GetUserOrgList(ctx, &query)
|
||||
if err != nil {
|
||||
@ -41,10 +59,10 @@ func (auth OrgIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attribut
|
||||
}
|
||||
|
||||
for _, org := range result {
|
||||
if org.OrgID == orgID {
|
||||
if org.OrgID == info.OrgID {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
return authorizer.DecisionDeny, fmt.Sprintf("user %d is not a member of org %d", signedInUser.UserID, orgID), nil
|
||||
return authorizer.DecisionDeny, fmt.Sprintf("user %d is not a member of org %d", signedInUser.UserID, info.OrgID), nil
|
||||
}
|
||||
|
@ -4,22 +4,20 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
var _ authorizer.Authorizer = &OrgIDAuthorizer{}
|
||||
|
||||
type OrgRoleAuthorizer struct {
|
||||
log log.Logger
|
||||
org org.Service
|
||||
}
|
||||
|
||||
func ProvideOrgRoleAuthorizer(orgService org.Service) *OrgRoleAuthorizer {
|
||||
return &OrgRoleAuthorizer{log: log.New("grafana-apiserver.authorizer.orgrole"), org: orgService}
|
||||
return &OrgRoleAuthorizer{log: log.New("grafana-apiserver.authorizer.orgrole")}
|
||||
}
|
||||
|
||||
func (auth OrgRoleAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
|
@ -7,16 +7,28 @@ import (
|
||||
"k8s.io/apiserver/pkg/authorization/union"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer/org"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer/stack"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
func ProvideAuthorizer(
|
||||
orgIDAuthorizer *org.OrgIDAuthorizer,
|
||||
orgRoleAuthorizer *org.OrgRoleAuthorizer,
|
||||
stackIDAuthorizer *stack.StackIDAuthorizer,
|
||||
cfg *setting.Cfg,
|
||||
) authorizer.Authorizer {
|
||||
authorizers := []authorizer.Authorizer{
|
||||
authorizerfactory.NewPrivilegedGroups(user.SystemPrivilegedGroup),
|
||||
orgIDAuthorizer,
|
||||
orgRoleAuthorizer,
|
||||
}
|
||||
|
||||
// In Hosted grafana, the StackID replaces the orgID as a valid namespace
|
||||
if cfg.StackID != "" {
|
||||
authorizers = append(authorizers, stackIDAuthorizer)
|
||||
} else {
|
||||
authorizers = append(authorizers, orgIDAuthorizer)
|
||||
}
|
||||
|
||||
authorizers = append(authorizers, orgRoleAuthorizer)
|
||||
|
||||
return union.New(authorizers...)
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
package stack
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var _ authorizer.Authorizer = &StackIDAuthorizer{}
|
||||
|
||||
type StackIDAuthorizer struct {
|
||||
log log.Logger
|
||||
stackID string
|
||||
}
|
||||
|
||||
func ProvideStackIDAuthorizer(cfg *setting.Cfg) *StackIDAuthorizer {
|
||||
return &StackIDAuthorizer{
|
||||
log: log.New("grafana-apiserver.authorizer.stackid"),
|
||||
stackID: cfg.StackID, // this lets a single tenant grafana validate stack id (rather than orgs)
|
||||
}
|
||||
}
|
||||
|
||||
func (auth StackIDAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
signedInUser, err := appcontext.User(ctx)
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, fmt.Sprintf("error getting signed in user: %v", err), nil
|
||||
}
|
||||
|
||||
info, err := grafanarequest.ParseNamespace(a.GetNamespace())
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, fmt.Sprintf("error reading namespace: %v", err), nil
|
||||
}
|
||||
|
||||
// No opinion when the namespace is arbitrary
|
||||
if info.OrgID == -1 {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
|
||||
if info.StackID != auth.stackID {
|
||||
return authorizer.DecisionDeny, "wrong stack id is selected", nil
|
||||
}
|
||||
if info.OrgID != 1 {
|
||||
return authorizer.DecisionDeny, "cloud instance requires org 1", nil
|
||||
}
|
||||
if signedInUser.OrgID != 1 {
|
||||
return authorizer.DecisionDeny, "user must be in org 1", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package stack
|
||||
|
||||
import "github.com/google/wire"
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
ProvideStackIDAuthorizer,
|
||||
)
|
@ -4,9 +4,11 @@ import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer/org"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer/stack"
|
||||
)
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
org.WireSet,
|
||||
stack.WireSet,
|
||||
ProvideAuthorizer,
|
||||
)
|
||||
|
@ -2,25 +2,58 @@ package request
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
)
|
||||
|
||||
func OrgIDFrom(ctx context.Context) (int64, bool) {
|
||||
ns := request.NamespaceValue(ctx)
|
||||
return ParseOrgID(ns)
|
||||
type NamespaceInfo struct {
|
||||
// OrgID defined in namespace (1 when using stack ids)
|
||||
OrgID int64
|
||||
|
||||
// The cloud stack ID (must match the value in cfg.Settings)
|
||||
StackID string
|
||||
|
||||
// The original namespace string regardless the input
|
||||
Value string
|
||||
}
|
||||
|
||||
func ParseOrgID(ns string) (int64, bool) {
|
||||
if len(ns) < 5 || ns[:4] != "org-" {
|
||||
return 0, false
|
||||
func NamespaceInfoFrom(ctx context.Context, requireOrgID bool) (NamespaceInfo, error) {
|
||||
info, err := ParseNamespace(request.NamespaceValue(ctx))
|
||||
if err == nil && requireOrgID && info.OrgID < 1 {
|
||||
return info, fmt.Errorf("expected valid orgId")
|
||||
}
|
||||
|
||||
orgID, err := strconv.Atoi(ns[4:])
|
||||
if err != nil {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
return int64(orgID), true
|
||||
return info, err
|
||||
}
|
||||
|
||||
func ParseNamespace(ns string) (NamespaceInfo, error) {
|
||||
info := NamespaceInfo{Value: ns, OrgID: -1}
|
||||
if ns == "default" {
|
||||
info.OrgID = 1
|
||||
return info, nil
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ns, "org-") {
|
||||
id, err := strconv.Atoi(ns[4:])
|
||||
if id < 1 {
|
||||
return info, fmt.Errorf("invalid org id")
|
||||
}
|
||||
if id == 1 {
|
||||
return info, fmt.Errorf("use default rather than org-1")
|
||||
}
|
||||
info.OrgID = int64(id)
|
||||
return info, err
|
||||
}
|
||||
|
||||
if strings.HasPrefix(ns, "stack-") {
|
||||
info.StackID = ns[6:]
|
||||
if len(info.StackID) < 2 {
|
||||
return info, fmt.Errorf("invalid stack id")
|
||||
}
|
||||
info.OrgID = 1
|
||||
return info, nil
|
||||
}
|
||||
return info, nil
|
||||
}
|
||||
|
@ -1,61 +1,127 @@
|
||||
package request_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apiserver/pkg/endpoints/request"
|
||||
|
||||
grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
)
|
||||
|
||||
func TestOrgIDFrom(t *testing.T) {
|
||||
func TestParseNamespace(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
ctx context.Context
|
||||
expected int64
|
||||
ok bool
|
||||
name string
|
||||
namespace string
|
||||
expected grafanarequest.NamespaceInfo
|
||||
expectErr bool
|
||||
}{
|
||||
{
|
||||
name: "empty namespace",
|
||||
ctx: context.Background(),
|
||||
expected: 0,
|
||||
ok: false,
|
||||
name: "empty namespace",
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "incorrect number of parts",
|
||||
ctx: request.WithNamespace(context.Background(), "org-123-a"),
|
||||
expected: 0,
|
||||
ok: false,
|
||||
name: "incorrect number of parts",
|
||||
namespace: "org-123-a",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "incorrect prefix",
|
||||
ctx: request.WithNamespace(context.Background(), "abc-123"),
|
||||
expected: 0,
|
||||
ok: false,
|
||||
name: "org id not a number",
|
||||
namespace: "org-invalid",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org id not a number",
|
||||
ctx: request.WithNamespace(context.Background(), "org-invalid"),
|
||||
expected: 0,
|
||||
ok: false,
|
||||
name: "valid org id",
|
||||
namespace: "org-123",
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: 123,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid org id",
|
||||
ctx: request.WithNamespace(context.Background(), "org-123"),
|
||||
expected: 123,
|
||||
ok: true,
|
||||
name: "org should not be 1 in the namespace",
|
||||
namespace: "org-1",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not be negative",
|
||||
namespace: "org--5",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "can not be zero",
|
||||
namespace: "org-0",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default is org 1",
|
||||
namespace: "default",
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid stack",
|
||||
namespace: "stack-abcdef",
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: 1,
|
||||
StackID: "abcdef",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid stack id",
|
||||
namespace: "stack-",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid stack id (too short)",
|
||||
namespace: "stack-1",
|
||||
expectErr: true,
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
StackID: "1",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "other namespace",
|
||||
namespace: "anything",
|
||||
expected: grafanarequest.NamespaceInfo{
|
||||
OrgID: -1,
|
||||
Value: "anything",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
actual, ok := grafanarequest.OrgIDFrom(tt.ctx)
|
||||
if actual != tt.expected {
|
||||
t.Errorf("OrgIDFrom() returned %d, expected %d", actual, tt.expected)
|
||||
info, err := grafanarequest.ParseNamespace(tt.namespace)
|
||||
if tt.expectErr != (err != nil) {
|
||||
t.Errorf("ParseNamespace() returned %+v, expected an error", info)
|
||||
}
|
||||
if ok != tt.ok {
|
||||
t.Errorf("OrgIDFrom() returned %t, expected %t", ok, tt.ok)
|
||||
if info.OrgID != tt.expected.OrgID {
|
||||
t.Errorf("ParseNamespace() [OrgID] returned %d, expected %d", info.OrgID, tt.expected.OrgID)
|
||||
}
|
||||
if info.StackID != tt.expected.StackID {
|
||||
t.Errorf("ParseNamespace() [StackID] returned %s, expected %s", info.StackID, tt.expected.StackID)
|
||||
}
|
||||
if info.Value != tt.namespace {
|
||||
t.Errorf("ParseNamespace() [Value] returned %s, expected %s", info.Value, tt.namespace)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -53,8 +53,17 @@ interface K8sPlaylist {
|
||||
}
|
||||
|
||||
class K8sAPI implements PlaylistAPI {
|
||||
readonly url = `/apis/playlist.x.grafana.com/v0alpha1/namespaces/org-${contextSrv.user.orgId}/playlists`;
|
||||
readonly legacy = new LegacyAPI(); // set to null for full CRUD
|
||||
readonly url: string;
|
||||
readonly legacy: PlaylistAPI | undefined;
|
||||
|
||||
constructor() {
|
||||
const ns = contextSrv.user.orgId === 1 ? 'default' : `org-${contextSrv.user.orgId}`;
|
||||
this.url = `/apis/playlist.x.grafana.com/v0alpha1/namespaces/${ns}/playlists`;
|
||||
|
||||
// When undefined, this will use k8s for all CRUD features
|
||||
// if (!config.featureToggles.grafanaAPIServerWithExperimentalAPIs) {
|
||||
this.legacy = new LegacyAPI();
|
||||
}
|
||||
|
||||
async getAllPlaylist(): Promise<Playlist[]> {
|
||||
const result = await getBackendSrv().get<K8sPlaylistList>(this.url);
|
||||
|
Loading…
Reference in New Issue
Block a user