K8S: cleanup and consolidate feature toggles (#63212)

This commit is contained in:
Ryan McKinley
2023-02-09 09:54:00 -08:00
committed by GitHub
parent 94241f6676
commit 0018c8e9c1
17 changed files with 2 additions and 455 deletions
-2
View File
@@ -93,7 +93,6 @@ import (
"github.com/grafana/grafana/pkg/services/stats"
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity/httpentitystore"
"github.com/grafana/grafana/pkg/services/store/k8saccess"
"github.com/grafana/grafana/pkg/services/tag"
"github.com/grafana/grafana/pkg/services/team"
"github.com/grafana/grafana/pkg/services/teamguardian"
@@ -258,7 +257,6 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi
annotationRepo annotations.Repository, tagService tag.Service, searchv2HTTPService searchV2.SearchHTTPService,
queryLibraryHTTPService querylibrary.HTTPService, queryLibraryService querylibrary.Service, oauthTokenService oauthtoken.OAuthTokenService,
statsService stats.Service, authnService authn.Service, pluginsCDNService *pluginscdn.Service,
k8saccess k8saccess.K8SAccess, // required so that the router is registered
starApi *starApi.API,
) (*HTTPServer, error) {
web.Env = cfg.Env
-2
View File
@@ -124,7 +124,6 @@ import (
"github.com/grafana/grafana/pkg/services/store"
"github.com/grafana/grafana/pkg/services/store/entity/httpentitystore"
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash"
"github.com/grafana/grafana/pkg/services/store/k8saccess"
"github.com/grafana/grafana/pkg/services/store/kind"
"github.com/grafana/grafana/pkg/services/store/resolver"
"github.com/grafana/grafana/pkg/services/store/sanitizer"
@@ -365,7 +364,6 @@ var wireBasicSet = wire.NewSet(
wire.Bind(new(tag.Service), new(*tagimpl.Service)),
authnimpl.ProvideService,
wire.Bind(new(authn.Service), new(*authnimpl.Service)),
k8saccess.ProvideK8SAccess,
supportbundlesimpl.ProvideService,
)
@@ -3,22 +3,12 @@ package service
import (
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/store/k8saccess"
)
func ProvideSimpleDashboardService(
features featuremgmt.FeatureToggles,
svc *DashboardServiceImpl,
k8s k8saccess.K8SAccess,
store entity.EntityStoreServer,
) dashboards.DashboardService {
if features.IsEnabled(featuremgmt.FlagK8sDashboards) {
if k8s.GetSystemClient() == nil {
panic("k8s dashboards requires the k8s client registered")
}
return k8saccess.NewDashboardService(svc, store)
}
return svc
}
-12
View File
@@ -119,18 +119,6 @@ var (
State: FeatureStateAlpha,
RequiresDevMode: true,
},
{
Name: "k8sDashboards",
Description: "Save dashboards via k8s",
State: FeatureStateAlpha,
RequiresDevMode: true,
},
{
Name: "apiserver",
Description: "Add a k8s API server proxy",
State: FeatureStateAlpha,
RequiresDevMode: true,
},
{
Name: "supportBundles",
Description: "Support bundles for troubleshooting",
-8
View File
@@ -91,14 +91,6 @@ const (
// Explore native k8s integrations
FlagK8s = "k8s"
// FlagK8sDashboards
// Save dashboards via k8s
FlagK8sDashboards = "k8sDashboards"
// FlagApiserver
// Add a k8s API server proxy
FlagApiserver = "apiserver"
// FlagSupportBundles
// Support bundles for troubleshooting
FlagSupportBundles = "supportBundles"
@@ -25,7 +25,6 @@ func TestFeatureToggleFiles(t *testing.T) {
"live-pipeline": true,
"live-service-web-worker": true,
"k8s": true, // Camel case does not like this one
"k8sDashboards": true, // or this one
}
t.Run("check registry constraints", func(t *testing.T) {
-10
View File
@@ -170,16 +170,6 @@ func (s *ServiceImpl) getServerAdminNode(c *contextmodel.ReqContext) *navtree.Na
Url: s.cfg.AppSubURL + "/admin/storage/export",
})
}
if s.features.IsEnabled(featuremgmt.FlagK8s) {
storage.Children = append(storage.Children, &navtree.NavLink{
Text: "Kubernetes",
Id: "k8s",
SubTitle: "Manage k8s storage",
Icon: "cube",
Url: s.cfg.AppSubURL + "/admin/storage/k8s",
})
}
}
if s.cfg.LDAPEnabled && hasAccess(ac.ReqGrafanaAdmin, ac.EvalPermission(ac.ActionLDAPStatusRead)) {
-100
View File
@@ -1,100 +0,0 @@
package k8saccess
import (
"net/http"
"net/url"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/web"
)
type clientWrapper struct {
err error
baseURL *url.URL
client *kubernetes.Clientset
config *rest.Config
httpClient *http.Client
}
func newClientWrapper(config *rest.Config) *clientWrapper {
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
url, _, err := defaultServerUrlFor(config)
wrapper := &clientWrapper{
config: config,
baseURL: url,
err: err,
}
if err == nil && config != nil {
// share the transport between all clients
wrapper.httpClient, wrapper.err = rest.HTTPClientFor(config)
if wrapper.err == nil {
wrapper.client, wrapper.err = kubernetes.NewForConfigAndClient(config, wrapper.httpClient)
}
}
return wrapper
}
func (s *clientWrapper) getInfo() map[string]interface{} {
info := make(map[string]interface{}, 0)
if s.err != nil {
info["error"] = s.err.Error()
}
if s.baseURL != nil {
info["baseURL"] = s.baseURL.String()
}
if s.client != nil {
v, err := s.client.ServerVersion()
if err != nil {
info["version_error"] = err.Error()
}
if v != nil {
info["k8s.version"] = v
}
}
return info
}
// defaultServerUrlFor is shared between IsConfigTransportTLS and RESTClientFor. It
// requires Host and Version to be set prior to being called.
func defaultServerUrlFor(config *rest.Config) (*url.URL, string, error) {
// TODO: move the default to secure when the apiserver supports TLS by default
// config.Insecure is taken to mean "I want HTTPS but don't bother checking the certs against a CA."
hasCA := len(config.CAFile) != 0 || len(config.CAData) != 0
hasCert := len(config.CertFile) != 0 || len(config.CertData) != 0
defaultTLS := hasCA || hasCert || config.Insecure
host := config.Host
if host == "" {
host = "localhost"
}
if config.GroupVersion != nil {
return rest.DefaultServerURL(host, config.APIPath, *config.GroupVersion, defaultTLS)
}
return rest.DefaultServerURL(host, config.APIPath, schema.GroupVersion{}, defaultTLS)
}
func (s *clientWrapper) doProxy(c *contextmodel.ReqContext) {
if s.baseURL == nil {
c.Resp.WriteHeader(500)
return
}
params := web.Params(c.Req)
path := params["*"]
url := s.baseURL.JoinPath(path)
_, _ = c.Resp.Write([]byte("TODO, proxy: " + url.String()))
}
@@ -1,93 +0,0 @@
package k8saccess
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/store/entity"
)
type k8sDashboardService struct {
orig dashboards.DashboardService
store entity.EntityStoreServer
}
var _ dashboards.DashboardService = (*k8sDashboardService)(nil)
func NewDashboardService(orig dashboards.DashboardService, store entity.EntityStoreServer) dashboards.DashboardService {
return &k8sDashboardService{
orig: orig,
store: store,
}
}
func (s *k8sDashboardService) BuildSaveDashboardCommand(ctx context.Context, dto *dashboards.SaveDashboardDTO, shouldValidateAlerts bool, validateProvisionedDashboard bool) (*dashboards.SaveDashboardCommand, error) {
return s.orig.BuildSaveDashboardCommand(ctx, dto, shouldValidateAlerts, validateProvisionedDashboard)
}
func (s *k8sDashboardService) DeleteDashboard(ctx context.Context, dashboardId int64, orgId int64) error {
return s.orig.DeleteDashboard(ctx, dashboardId, orgId)
}
func (s *k8sDashboardService) FindDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) ([]dashboards.DashboardSearchProjection, error) {
return s.orig.FindDashboards(ctx, query)
}
func (s *k8sDashboardService) GetDashboard(ctx context.Context, query *dashboards.GetDashboardQuery) (*dashboards.Dashboard, error) {
return s.orig.GetDashboard(ctx, query)
}
func (s *k8sDashboardService) GetDashboardACLInfoList(ctx context.Context, query *dashboards.GetDashboardACLInfoListQuery) ([]*dashboards.DashboardACLInfoDTO, error) {
return s.orig.GetDashboardACLInfoList(ctx, query)
}
func (s *k8sDashboardService) GetDashboards(ctx context.Context, query *dashboards.GetDashboardsQuery) ([]*dashboards.Dashboard, error) {
return s.orig.GetDashboards(ctx, query)
}
func (s *k8sDashboardService) GetDashboardTags(ctx context.Context, query *dashboards.GetDashboardTagsQuery) ([]*dashboards.DashboardTagCloudItem, error) {
return s.orig.GetDashboardTags(ctx, query)
}
func (s *k8sDashboardService) GetDashboardUIDByID(ctx context.Context, query *dashboards.GetDashboardRefByIDQuery) (*dashboards.DashboardRef, error) {
return s.orig.GetDashboardUIDByID(ctx, query)
}
func (s *k8sDashboardService) HasAdminPermissionInDashboardsOrFolders(ctx context.Context, query *folder.HasAdminPermissionInDashboardsOrFoldersQuery) (bool, error) {
return s.orig.HasAdminPermissionInDashboardsOrFolders(ctx, query)
}
func (s *k8sDashboardService) HasEditPermissionInFolders(ctx context.Context, query *folder.HasEditPermissionInFoldersQuery) (bool, error) {
return s.orig.HasEditPermissionInFolders(ctx, query)
}
func (s *k8sDashboardService) ImportDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO) (*dashboards.Dashboard, error) {
return s.orig.ImportDashboard(ctx, dto)
}
func (s *k8sDashboardService) MakeUserAdmin(ctx context.Context, orgID int64, userID, dashboardID int64, setViewAndEditPermissions bool) error {
return s.orig.MakeUserAdmin(ctx, orgID, userID, dashboardID, setViewAndEditPermissions)
}
func (s *k8sDashboardService) SaveDashboard(ctx context.Context, dto *dashboards.SaveDashboardDTO, allowUiUpdate bool) (*dashboards.Dashboard, error) {
fmt.Printf("SAVE: " + dto.Dashboard.UID)
return s.orig.SaveDashboard(ctx, dto, allowUiUpdate)
}
func (s *k8sDashboardService) SearchDashboards(ctx context.Context, query *dashboards.FindPersistedDashboardsQuery) error {
return s.orig.SearchDashboards(ctx, query)
}
func (s *k8sDashboardService) UpdateDashboardACL(ctx context.Context, uid int64, items []*dashboards.DashboardACL) error {
return s.orig.UpdateDashboardACL(ctx, uid, items)
}
func (s *k8sDashboardService) DeleteACLByUser(ctx context.Context, userID int64) error {
return s.orig.DeleteACLByUser(ctx, userID)
}
func (s *k8sDashboardService) CountDashboardsInFolder(ctx context.Context, query *dashboards.CountDashboardsInFolderQuery) (int64, error) {
return s.orig.CountDashboardsInFolder(ctx, query)
}
-52
View File
@@ -1,52 +0,0 @@
package k8saccess
import (
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/middleware"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
)
type httpHelper struct {
access *k8sAccess
}
func newHTTPHelper(access *k8sAccess, router routing.RouteRegister) *httpHelper {
s := &httpHelper{
access: access,
}
// Must be admin for everything
router.Group("/api/k8s", func(k8sRoute routing.RouteRegister) {
k8sRoute.Get("/info", middleware.ReqOrgAdmin, routing.Wrap(s.showClientInfo))
k8sRoute.Any("/proxy/*", middleware.ReqOrgAdmin, s.doProxy)
})
return s
}
func (s *httpHelper) showClientInfo(c *contextmodel.ReqContext) response.Response {
if s.access.sys != nil {
info := s.access.sys.getInfo()
if s.access.sys.err != nil {
return response.JSON(500, info)
}
return response.JSON(200, info)
}
return response.JSON(500, map[string]interface{}{
"error": "no client initialized",
})
}
func (s *httpHelper) doProxy(c *contextmodel.ReqContext) {
// TODO... this does not yet do a real proxy
if s.access.sys != nil {
if s.access.sys.err == nil {
s.access.sys.doProxy(c)
} else {
c.Resp.WriteHeader(500)
}
return
}
_, _ = c.Resp.Write([]byte("??"))
}
-81
View File
@@ -1,81 +0,0 @@
package k8saccess
import (
"os"
"path/filepath"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/featuremgmt"
)
type K8SAccess interface {
registry.CanBeDisabled
// Get the system client
GetSystemClient() *kubernetes.Clientset
}
var _ K8SAccess = &k8sAccess{}
type k8sAccess struct {
enabled bool
apihelper *httpHelper
sys *clientWrapper
}
func ProvideK8SAccess(toggles featuremgmt.FeatureToggles, router routing.RouteRegister) K8SAccess {
access := &k8sAccess{
enabled: toggles.IsEnabled(featuremgmt.FlagK8s),
}
// Skips setting up any HTTP routing
if !access.enabled {
return access // dummy
}
// If we are in a cluster, this is the
config, err := rest.InClusterConfig()
// Look for kube config setup
if err != nil {
var home string
var configBytes []byte
home, err = os.UserHomeDir()
if err == nil {
fpath := filepath.Join(home, ".kube", "config")
//nolint:gosec
configBytes, err = os.ReadFile(fpath)
if err == nil {
config, err = clientcmd.RESTConfigFromKubeConfig(configBytes)
}
}
}
if err == nil && config != nil {
access.sys = newClientWrapper(config)
} else {
access.sys = &clientWrapper{
err: err,
}
}
access.apihelper = newHTTPHelper(access, router)
return access
}
func (s *k8sAccess) IsDisabled() bool {
return !s.enabled
}
// Return access to the system k8s client
func (s *k8sAccess) GetSystemClient() *kubernetes.Clientset {
if s.sys != nil {
return s.sys.client
}
return nil
}