K8s: Refactor config/options for aggregation (#81739)

This commit is contained in:
Todd Treece 2024-02-01 17:27:30 -05:00 committed by GitHub
parent 7a17963ab9
commit 67b6be5515
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
93 changed files with 1104 additions and 1448 deletions

3
.github/CODEOWNERS vendored
View File

@ -108,7 +108,7 @@
/pkg/services/dashboardversion/ @grafana/backend-platform
/pkg/services/encryption/ @grafana/backend-platform
/pkg/services/folder/ @grafana/backend-platform
/pkg/services/grafana-apiserver @grafana/grafana-app-platform-squad
/pkg/services/apiserver @grafana/grafana-app-platform-squad
/pkg/services/hooks/ @grafana/backend-platform
/pkg/services/kmsproviders/ @grafana/backend-platform
/pkg/services/licensing/ @grafana/backend-platform
@ -275,7 +275,6 @@
/pkg/modules/ @grafana/grafana-app-platform-squad
/pkg/kindsysreport/ @grafana/grafana-app-platform-squad
/pkg/services/grpcserver/ @grafana/grafana-app-platform-squad
/pkg/aggregator @grafana/grafana-app-platform-squad
/pkg/generated @grafana/grafana-app-platform-squad
# Alerting

View File

@ -1,628 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/aggregator.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/server.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/pkg/controlplane/apiserver/apiextensions.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
package aggregator
import (
"crypto/tls"
"fmt"
"io"
"net"
"net/http"
"path"
"strings"
"sync"
"time"
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
serviceclientset "github.com/grafana/grafana/pkg/generated/clientset/versioned"
informersv0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions"
"github.com/grafana/grafana/pkg/registry/apis/service"
grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver"
filestorage "github.com/grafana/grafana/pkg/services/grafana-apiserver/storage/file"
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
apiextensionsinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
apiextensionsopenapi "k8s.io/apiextensions-apiserver/pkg/generated/openapi"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
genericfeatures "k8s.io/apiserver/pkg/features"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/server/resourceconfig"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/apiserver/pkg/util/openapi"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
apiregistrationclientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1"
apiregistrationInformers "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1"
"k8s.io/kube-aggregator/pkg/controllers/autoregister"
apiserver "k8s.io/kube-aggregator/pkg/controllers/status"
aggregatoropenapi "k8s.io/kube-aggregator/pkg/generated/openapi"
"k8s.io/kube-openapi/pkg/common"
)
// AggregatorServerOptions contains the state for the aggregator apiserver
type AggregatorServerOptions struct {
Builders []grafanaAPIServer.APIGroupBuilder
AlternateDNS []string
Config *Config
serviceResolver ServiceResolver
sharedInformerFactory informersv0alpha1.SharedInformerFactory
StdOut io.Writer
StdErr io.Writer
}
func NewAggregatorServerOptions(out, errOut io.Writer,
options *options.RecommendedOptions,
extraConfig *ExtraConfig,
) (*AggregatorServerOptions, error) {
sharedConfig, err := initSharedConfig(options, aggregatorscheme.Codecs, nil)
if err != nil {
klog.Errorf("Error creating shared config: %s", err)
return nil, err
}
sharedInformerFactory, err := initSharedInformerFactory(sharedConfig)
if err != nil {
klog.Errorf("Error creating shared informer factory: %s", err)
return nil, err
}
serviceResolver, err := initServiceResolver(sharedInformerFactory)
if err != nil {
klog.Errorf("Error creating service resolver: %s", err)
return nil, err
}
fakeInformers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(), 10*time.Minute)
builders := []grafanaAPIServer.APIGroupBuilder{
service.NewServiceAPIBuilder(),
}
extensionsConfig, err := initApiExtensionsConfig(options, sharedConfig, fakeInformers, serviceResolver, extraConfig.DataPath)
if err != nil {
klog.Errorf("Error creating extensions config: %s", err)
return nil, err
}
aggregatorConfig, err := initAggregatorConfig(options, sharedConfig, extraConfig, fakeInformers, builders, serviceResolver, extraConfig.DataPath)
if err != nil {
klog.Errorf("Error creating aggregator config: %s", err)
return nil, err
}
return &AggregatorServerOptions{
StdOut: out,
StdErr: errOut,
Builders: builders,
sharedInformerFactory: sharedInformerFactory,
serviceResolver: serviceResolver,
Config: &Config{
Aggregator: aggregatorConfig,
ApiExtensions: extensionsConfig,
SharedConfig: sharedConfig,
extraConfig: extraConfig,
},
}, nil
}
func (o *AggregatorServerOptions) LoadAPIGroupBuilders() error {
// Install schemas
for _, b := range o.Builders {
if err := b.InstallSchema(aggregatorscheme.Scheme); err != nil {
return err
}
}
return nil
}
func initSharedConfig(options *options.RecommendedOptions, codecs serializer.CodecFactory, alternateDNS []string) (*genericapiserver.RecommendedConfig, error) {
if err := options.SecureServing.MaybeDefaultWithSelfSignedCerts(
"localhost", alternateDNS, []net.IP{net.IPv4(127, 0, 0, 1)},
); err != nil {
return nil, fmt.Errorf("error creating self-signed certificates: %v", err)
}
options.Authentication.RemoteKubeConfigFileOptional = true
options.Authorization.RemoteKubeConfigFileOptional = true
options.Admission = nil
if options.CoreAPI.CoreAPIKubeconfigPath == "" {
options.CoreAPI = nil
}
serverConfig := genericapiserver.NewRecommendedConfig(codecs)
// NOTE: AggregatedDiscoveryGroupManager in kube-apiserver is set up by controlplane APIServerConfig creation
// Here, we adopt that one line in addition to what recommendedOptions gives us
// Without it, CRDs work on API routes (and are registered in openapi) but not discoverable by kubectl
serverConfig.AggregatedDiscoveryGroupManager = aggregated.NewResourceManager("apis")
if options.CoreAPI == nil {
if err := modifiedApplyTo(options, serverConfig); err != nil {
return nil, err
}
} else {
if err := options.ApplyTo(serverConfig); err != nil {
return nil, err
}
}
return serverConfig, nil
}
// A copy of ApplyTo in recommended.go, but for >= 0.28, server pkg in apiserver does a bit extra causing
// a panic when CoreAPI is set to nil
func modifiedApplyTo(options *options.RecommendedOptions, config *genericapiserver.RecommendedConfig) error {
if err := options.Etcd.ApplyTo(&config.Config); err != nil {
return err
}
if err := options.EgressSelector.ApplyTo(&config.Config); err != nil {
return err
}
if err := options.Traces.ApplyTo(config.Config.EgressSelector, &config.Config); err != nil {
return err
}
if err := options.SecureServing.ApplyTo(&config.Config.SecureServing, &config.Config.LoopbackClientConfig); err != nil {
return err
}
if err := options.Authentication.ApplyTo(&config.Config.Authentication, config.SecureServing, config.OpenAPIConfig); err != nil {
return err
}
if err := options.Authorization.ApplyTo(&config.Config.Authorization); err != nil {
return err
}
if err := options.Audit.ApplyTo(&config.Config); err != nil {
return err
}
// TODO: determine whether we need flow control (API priority and fairness)
//if err := options.Features.ApplyTo(&config.Config); err != nil {
// return err
//}
if err := options.CoreAPI.ApplyTo(config); err != nil {
return err
}
_, err := options.ExtraAdmissionInitializers(config)
if err != nil {
return err
}
return nil
}
func getMergedOpenAPIDefinitions(builders []grafanaAPIServer.APIGroupBuilder, ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
// Add OpenAPI specs for each group+version
prerequisiteAPIs := grafanaAPIServer.GetOpenAPIDefinitions(builders)(ref)
aggregatorAPIs := aggregatoropenapi.GetOpenAPIDefinitions(ref)
for k, v := range prerequisiteAPIs {
aggregatorAPIs[k] = v
}
return aggregatorAPIs
}
func initSharedInformerFactory(sharedConfig *genericapiserver.RecommendedConfig) (informersv0alpha1.SharedInformerFactory, error) {
serviceClient, err := serviceclientset.NewForConfig(sharedConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
return informersv0alpha1.NewSharedInformerFactory(
serviceClient,
5*time.Minute, // this is effectively used as a refresh interval right now. Might want to do something nicer later on.
), nil
}
func initServiceResolver(factory informersv0alpha1.SharedInformerFactory) (apiserver.ServiceResolver, error) {
return NewExternalNameResolver(factory.Service().V0alpha1().ExternalNames().Lister()), nil
}
func initApiExtensionsConfig(options *options.RecommendedOptions,
sharedConfig *genericapiserver.RecommendedConfig,
fakeInfomers informers.SharedInformerFactory,
serviceResolver apiserver.ServiceResolver,
dataPath string,
) (*apiextensionsapiserver.Config, error) {
// make a shallow copy to let us twiddle a few things
// most of the config actually remains the same. We only need to mess with a couple items related to the particulars of the api extensions
genericConfig := sharedConfig.Config
genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
genericConfig.RESTOptionsGetter = nil
// copy the etcd options so we don't mutate originals.
// we assume that the etcd options have been completed already. avoid messing with anything outside
// of changes to StorageConfig as that may lead to unexpected behavior when the options are applied.
etcdOptions := *options.Etcd
// this is where the true decodable levels come from.
etcdOptions.StorageConfig.Codec = apiextensionsapiserver.Codecs.LegacyCodec(apiextensionsv1beta1.SchemeGroupVersion, v1.SchemeGroupVersion)
// prefer the more compact serialization (v1beta1) for storage until https://issue.k8s.io/82292 is resolved for objects whose v1 serialization is too big but whose v1beta1 serialization can be stored
etcdOptions.StorageConfig.EncodeVersioner = runtime.NewMultiGroupVersioner(apiextensionsv1beta1.SchemeGroupVersion, schema.GroupKind{Group: apiextensionsv1beta1.GroupName})
etcdOptions.SkipHealthEndpoints = true // avoid double wiring of health checks
if err := etcdOptions.ApplyTo(&genericConfig); err != nil {
return nil, err
}
restOptionsGetter := filestorage.NewRESTOptionsGetter(path.Join(dataPath, "grafana-apiextensionsserver"), etcdOptions.StorageConfig)
genericConfig.RESTOptionsGetter = restOptionsGetter
// NOTE: ignoring genericConfig.ResourceTransformers in crdOptionsGetter creation for now
// crdOptionsGetter := apiextensionsoptions.NewCRDRESTOptionsGetter(etcdOptions, genericConfig.ResourceTransformers, )
// The following is equivalent code to apiextensionsoptions.NewCRDRESTOptionsGetter with lesser dependencies
crdEtcdOptions := etcdOptions
crdEtcdOptions.StorageConfig.Codec = unstructured.UnstructuredJSONScheme
crdEtcdOptions.StorageConfig.StorageObjectCountTracker = genericConfig.StorageObjectCountTracker
crdEtcdOptions.WatchCacheSizes = nil // this control is not provided for custom resources
// override MergedResourceConfig with apiextensions defaults and registry
mergedResourceConfig, err := resourceconfig.MergeAPIResourceConfigs(apiextensionsapiserver.DefaultAPIResourceConfigSource(), nil, apiextensionsapiserver.Scheme)
if err != nil {
return nil, err
}
genericConfig.MergedResourceConfig = mergedResourceConfig
genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(apiextensionsopenapi.GetOpenAPIDefinitions), openapinamer.NewDefinitionNamer(apiextensionsapiserver.Scheme, apiextensionsapiserver.Scheme))
apiextensionsConfig := &apiextensionsapiserver.Config{
GenericConfig: &genericapiserver.RecommendedConfig{
Config: genericConfig,
SharedInformerFactory: fakeInfomers,
},
ExtraConfig: apiextensionsapiserver.ExtraConfig{
CRDRESTOptionsGetter: filestorage.NewRESTOptionsGetter(path.Join(dataPath, "grafana-apiextensionsserver"), crdEtcdOptions.StorageConfig),
// TODO: remove the hardcod when HA story is more developed
MasterCount: 1,
// TODO: leaving AuthResolverWrapper unset doesn't impact basic operation of CRDs
// AuthResolverWrapper: authResolverWrapper,
ServiceResolver: serviceResolver,
},
}
// we need to clear the poststarthooks so we don't add them multiple times to all the servers (that fails)
apiextensionsConfig.GenericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
return apiextensionsConfig, nil
}
func initAggregatorConfig(options *options.RecommendedOptions,
sharedConfig *genericapiserver.RecommendedConfig,
extra *ExtraConfig,
fakeInformers informers.SharedInformerFactory,
builders []grafanaAPIServer.APIGroupBuilder,
serviceResolver apiserver.ServiceResolver,
dataPath string,
) (*aggregatorapiserver.Config, error) {
// make a shallow copy to let us twiddle a few things
// most of the config actually remains the same. We only need to mess with a couple items related to the particulars of the aggregator
genericConfig := sharedConfig.Config
genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
genericConfig.RESTOptionsGetter = nil
// prevent generic API server from installing the OpenAPI handler. Aggregator server
// has its own customized OpenAPI handler.
genericConfig.SkipOpenAPIInstallation = true
mergedResourceConfig, err := resourceconfig.MergeAPIResourceConfigs(aggregatorapiserver.DefaultAPIResourceConfigSource(), nil, aggregatorscheme.Scheme)
if err != nil {
return nil, err
}
genericConfig.MergedResourceConfig = mergedResourceConfig
getOpenAPIDefinitionsFunc := func() common.GetOpenAPIDefinitions {
return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
return getMergedOpenAPIDefinitions(builders, ref)
}
}
namer := openapinamer.NewDefinitionNamer(aggregatorscheme.Scheme, apiextensionsapiserver.Scheme)
genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(getOpenAPIDefinitionsFunc(), namer)
genericConfig.OpenAPIV3Config.Info.Title = "Kubernetes"
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(getOpenAPIDefinitionsFunc(), namer)
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) &&
utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) {
// Add StorageVersionPrecondition handler to aggregator-apiserver.
// The handler will block write requests to built-in resources until the
// target resources' storage versions are up-to-date.
genericConfig.BuildHandlerChainFunc = genericapiserver.BuildHandlerChainWithStorageVersionPrecondition
}
// copy the etcd options so we don't mutate originals.
// we assume that the etcd options have been completed already. avoid messing with anything outside
// of changes to StorageConfig as that may lead to unexpected behavior when the options are applied.
etcdOptions := *options.Etcd
etcdOptions.StorageConfig.Codec = aggregatorscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion,
apiregistrationv1beta1.SchemeGroupVersion,
servicev0alpha1.SchemeGroupVersion)
etcdOptions.StorageConfig.EncodeVersioner = runtime.NewMultiGroupVersioner(v1.SchemeGroupVersion,
schema.GroupKind{Group: apiregistrationv1beta1.GroupName},
schema.GroupKind{Group: servicev0alpha1.GROUP})
etcdOptions.SkipHealthEndpoints = true // avoid double wiring of health checks
if err := etcdOptions.ApplyTo(&genericConfig); err != nil {
return nil, err
}
genericConfig.RESTOptionsGetter = filestorage.NewRESTOptionsGetter(path.Join(dataPath, "grafana-aggregator"), etcdOptions.StorageConfig)
genericConfig.DisabledPostStartHooks = genericConfig.DisabledPostStartHooks.Insert("apiservice-status-available-controller")
genericConfig.DisabledPostStartHooks = genericConfig.DisabledPostStartHooks.Insert("start-kube-aggregator-informers")
aggregatorConfig := &aggregatorapiserver.Config{
GenericConfig: &genericapiserver.RecommendedConfig{
Config: genericConfig,
SharedInformerFactory: fakeInformers,
ClientConfig: genericConfig.LoopbackClientConfig,
},
ExtraConfig: aggregatorapiserver.ExtraConfig{
ProxyClientCertFile: extra.ProxyClientCertFile,
ProxyClientKeyFile: extra.ProxyClientKeyFile,
// NOTE: while ProxyTransport can be skipped in the configuration, it allows honoring
// DISABLE_HTTP2, HTTPS_PROXY and NO_PROXY env vars as needed
ProxyTransport: createProxyTransport(),
},
}
aggregatorConfig.ExtraConfig.ServiceResolver = serviceResolver
// we need to clear the poststarthooks so we don't add them multiple times to all the servers (that fails)
aggregatorConfig.GenericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
return aggregatorConfig, nil
}
func (o *AggregatorServerOptions) CreateAggregatorServer(delegateAPIServer genericapiserver.DelegationTarget, apiExtensionsInformers apiextensionsinformers.SharedInformerFactory) (*aggregatorapiserver.APIAggregator, error) {
completedConfig := o.Config.AggregatorComplete
aggregatorServer, err := completedConfig.NewWithDelegate(delegateAPIServer)
if err != nil {
return nil, err
}
// create controllers for auto-registration
apiRegistrationClient, err := apiregistrationclient.NewForConfig(completedConfig.GenericConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
autoRegistrationController := autoregister.NewAutoRegisterController(aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), apiRegistrationClient)
apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController)
crdRegistrationController := NewCRDRegistrationController(
apiExtensionsInformers.Apiextensions().V1().CustomResourceDefinitions(),
autoRegistrationController)
// Imbue all builtin group-priorities onto the aggregated discovery
if completedConfig.GenericConfig.AggregatedDiscoveryGroupManager != nil {
for gv, entry := range apiVersionPriorities {
completedConfig.GenericConfig.AggregatedDiscoveryGroupManager.SetGroupVersionPriority(metav1.GroupVersion(gv), int(entry.group), int(entry.version))
}
}
err = aggregatorServer.GenericAPIServer.AddPostStartHook("kube-apiserver-autoregistration", func(context genericapiserver.PostStartHookContext) error {
go crdRegistrationController.Run(5, context.StopCh)
go func() {
crdRegistrationController.WaitForInitialSync()
autoRegistrationController.Run(5, context.StopCh)
}()
return nil
})
if err != nil {
return nil, err
}
err = aggregatorServer.GenericAPIServer.AddBootSequenceHealthChecks(
makeAPIServiceAvailableHealthCheck(
"autoregister-completion",
apiServices,
aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(),
),
)
if err != nil {
return nil, err
}
apiregistrationClient, err := apiregistrationclientset.NewForConfig(completedConfig.GenericConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
availableController, err := NewAvailableConditionController(
aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(),
o.sharedInformerFactory.Service().V0alpha1().ExternalNames(),
apiregistrationClient.ApiregistrationV1(),
nil,
(func() ([]byte, []byte))(nil),
completedConfig.ExtraConfig.ServiceResolver,
)
if err != nil {
return nil, err
}
aggregatorServer.GenericAPIServer.AddPostStartHookOrDie("apiservice-status-override-available-controller", func(context genericapiserver.PostStartHookContext) error {
// if we end up blocking for long periods of time, we may need to increase workers.
go availableController.Run(5, context.StopCh)
return nil
})
aggregatorServer.GenericAPIServer.AddPostStartHookOrDie("start-grafana-aggregator-informers", func(context genericapiserver.PostStartHookContext) error {
o.sharedInformerFactory.Start(context.StopCh)
aggregatorServer.APIRegistrationInformers.Start(context.StopCh)
return nil
})
// Install the API Group+version
for _, b := range o.Builders {
g, err := b.GetAPIGroupInfo(aggregatorscheme.Scheme, aggregatorscheme.Codecs, completedConfig.GenericConfig.RESTOptionsGetter)
if err != nil {
return nil, err
}
if g == nil || len(g.PrioritizedVersions) < 1 {
continue
}
err = aggregatorServer.GenericAPIServer.InstallAPIGroup(g)
if err != nil {
return nil, err
}
}
return aggregatorServer, nil
}
func makeAPIService(gv schema.GroupVersion) *v1.APIService {
apiServicePriority, ok := apiVersionPriorities[gv]
if !ok {
// if we aren't found, then we shouldn't register ourselves because it could result in a CRD group version
// being permanently stuck in the APIServices list.
klog.Infof("Skipping APIService creation for %v", gv)
return nil
}
return &v1.APIService{
ObjectMeta: metav1.ObjectMeta{Name: gv.Version + "." + gv.Group},
Spec: v1.APIServiceSpec{
Group: gv.Group,
Version: gv.Version,
GroupPriorityMinimum: apiServicePriority.group,
VersionPriority: apiServicePriority.version,
},
}
}
// makeAPIServiceAvailableHealthCheck returns a healthz check that returns healthy
// once all of the specified services have been observed to be available at least once.
func makeAPIServiceAvailableHealthCheck(name string, apiServices []*v1.APIService, apiServiceInformer apiregistrationInformers.APIServiceInformer) healthz.HealthChecker {
// Track the auto-registered API services that have not been observed to be available yet
pendingServiceNamesLock := &sync.RWMutex{}
pendingServiceNames := sets.NewString()
for _, service := range apiServices {
pendingServiceNames.Insert(service.Name)
}
// When an APIService in the list is seen as available, remove it from the pending list
handleAPIServiceChange := func(service *v1.APIService) {
pendingServiceNamesLock.Lock()
defer pendingServiceNamesLock.Unlock()
if !pendingServiceNames.Has(service.Name) {
return
}
if v1helper.IsAPIServiceConditionTrue(service, v1.Available) {
pendingServiceNames.Delete(service.Name)
}
}
// Watch add/update events for APIServices
_, _ = apiServiceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { handleAPIServiceChange(obj.(*v1.APIService)) },
UpdateFunc: func(old, new interface{}) { handleAPIServiceChange(new.(*v1.APIService)) },
})
// Don't return healthy until the pending list is empty
return healthz.NamedCheck(name, func(r *http.Request) error {
pendingServiceNamesLock.RLock()
defer pendingServiceNamesLock.RUnlock()
if pendingServiceNames.Len() > 0 {
return fmt.Errorf("missing APIService: %v", pendingServiceNames.List())
}
return nil
})
}
// priority defines group priority that is used in discovery. This controls
// group position in the kubectl output.
type priority struct {
// group indicates the order of the group relative to other groups.
group int32
// version indicates the relative order of the version inside of its group.
version int32
}
// The proper way to resolve this letting the aggregator know the desired group and version-within-group order of the underlying servers
// is to refactor the genericapiserver.DelegationTarget to include a list of priorities based on which APIs were installed.
// This requires the APIGroupInfo struct to evolve and include the concept of priorities and to avoid mistakes, the core storage map there needs to be updated.
// That ripples out every bit as far as you'd expect, so for 1.7 we'll include the list here instead of being built up during storage.
var apiVersionPriorities = map[schema.GroupVersion]priority{
{Group: "", Version: "v1"}: {group: 18000, version: 1},
// to my knowledge, nothing below here collides
{Group: "admissionregistration.k8s.io", Version: "v1"}: {group: 16700, version: 15},
{Group: "admissionregistration.k8s.io", Version: "v1beta1"}: {group: 16700, version: 12},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1"}: {group: 16700, version: 9},
{Group: "apiextensions.k8s.io", Version: "v1"}: {group: 16700, version: 15},
// Append a new group to the end of the list if unsure.
// You can use min(existing group)-100 as the initial value for a group.
// Version can be set to 9 (to have space around) for a new group.
}
func apiServicesToRegister(delegateAPIServer genericapiserver.DelegationTarget, registration autoregister.AutoAPIServiceRegistration) []*v1.APIService {
apiServices := []*v1.APIService{}
for _, curr := range delegateAPIServer.ListedPaths() {
if curr == "/api/v1" {
apiService := makeAPIService(schema.GroupVersion{Group: "", Version: "v1"})
registration.AddAPIServiceToSyncOnStart(apiService)
apiServices = append(apiServices, apiService)
continue
}
if !strings.HasPrefix(curr, "/apis/") {
continue
}
// this comes back in a list that looks like /apis/rbac.authorization.k8s.io/v1alpha1
tokens := strings.Split(curr, "/")
if len(tokens) != 4 {
continue
}
apiService := makeAPIService(schema.GroupVersion{Group: tokens[2], Version: tokens[3]})
if apiService == nil {
continue
}
registration.AddAPIServiceToSyncOnStart(apiService)
apiServices = append(apiServices, apiService)
}
return apiServices
}
// NOTE: below function imported from https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/server.go#L197
// createProxyTransport creates the dialer infrastructure to connect to the api servers.
func createProxyTransport() *http.Transport {
// NOTE: We don't set proxyDialerFn but the below SetTransportDefaults will
// See https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/net/http.go#L109
var proxyDialerFn utilnet.DialFunc
// Proxying to services is IP-based... don't expect to be able to verify the hostname
proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true}
proxyTransport := utilnet.SetTransportDefaults(&http.Transport{
DialContext: proxyDialerFn,
TLSClientConfig: proxyTLSClientConfig,
})
return proxyTransport
}

View File

@ -1,57 +0,0 @@
package aggregator
import (
"github.com/spf13/pflag"
apiextensionsapiserver "k8s.io/apiextensions-apiserver/pkg/apiserver"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
)
type ExtraConfig struct {
ProxyClientCertFile string
ProxyClientKeyFile string
DataPath string
}
type Config struct {
Aggregator *aggregatorapiserver.Config
ApiExtensions *apiextensionsapiserver.Config
AggregatorComplete aggregatorapiserver.CompletedConfig
ApiExtensionsComplete apiextensionsapiserver.CompletedConfig
recommendedOptions *options.RecommendedOptions
SharedConfig *genericapiserver.RecommendedConfig
extraConfig *ExtraConfig
}
func (c *Config) AddFlags(fs *pflag.FlagSet) {
if c == nil {
return
}
c.recommendedOptions.AddFlags(fs)
}
func (c *Config) Complete() {
if c == nil {
return
}
c.ApiExtensionsComplete = c.ApiExtensions.Complete()
c.AggregatorComplete = c.Aggregator.Complete()
}
func (ec *ExtraConfig) AddFlags(fs *pflag.FlagSet) {
if ec == nil {
return
}
fs.StringVar(&ec.ProxyClientCertFile, "proxy-client-cert-file", ec.ProxyClientCertFile,
"path to proxy client cert file")
fs.StringVar(&ec.ProxyClientKeyFile, "proxy-client-key-file", ec.ProxyClientKeyFile,
"path to proxy client cert file")
}

View File

@ -1,214 +0,0 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/pkg/controlplane/controller/crdregistration/crdregistration_controller.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
package aggregator
import (
"fmt"
"time"
"k8s.io/klog/v2"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
crdinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions/apiextensions/v1"
crdlisters "k8s.io/apiextensions-apiserver/pkg/client/listers/apiextensions/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
)
// AutoAPIServiceRegistration is an interface which callers can re-declare locally and properly cast to for
// adding and removing APIServices
type AutoAPIServiceRegistration interface {
// AddAPIServiceToSync adds an API service to auto-register.
AddAPIServiceToSync(in *v1.APIService)
// RemoveAPIServiceToSync removes an API service to auto-register.
RemoveAPIServiceToSync(name string)
}
type crdRegistrationController struct {
crdLister crdlisters.CustomResourceDefinitionLister
crdSynced cache.InformerSynced
apiServiceRegistration AutoAPIServiceRegistration
syncHandler func(groupVersion schema.GroupVersion) error
syncedInitialSet chan struct{}
// queue is where incoming work is placed to de-dup and to allow "easy" rate limited requeues on errors
// this is actually keyed by a groupVersion
queue workqueue.RateLimitingInterface
}
// NewCRDRegistrationController returns a controller which will register CRD GroupVersions with the auto APIService registration
// controller so they automatically stay in sync.
func NewCRDRegistrationController(crdinformer crdinformers.CustomResourceDefinitionInformer, apiServiceRegistration AutoAPIServiceRegistration) *crdRegistrationController {
c := &crdRegistrationController{
crdLister: crdinformer.Lister(),
crdSynced: crdinformer.Informer().HasSynced,
apiServiceRegistration: apiServiceRegistration,
syncedInitialSet: make(chan struct{}),
queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "crd_autoregistration_controller"),
}
c.syncHandler = c.handleVersionUpdate
_, _ = crdinformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
cast := obj.(*apiextensionsv1.CustomResourceDefinition)
c.enqueueCRD(cast)
},
UpdateFunc: func(oldObj, newObj interface{}) {
// Enqueue both old and new object to make sure we remove and add appropriate API services.
// The working queue will resolve any duplicates and only changes will stay in the queue.
c.enqueueCRD(oldObj.(*apiextensionsv1.CustomResourceDefinition))
c.enqueueCRD(newObj.(*apiextensionsv1.CustomResourceDefinition))
},
DeleteFunc: func(obj interface{}) {
cast, ok := obj.(*apiextensionsv1.CustomResourceDefinition)
if !ok {
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
if !ok {
klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
return
}
cast, ok = tombstone.Obj.(*apiextensionsv1.CustomResourceDefinition)
if !ok {
klog.V(2).Infof("Tombstone contained unexpected object: %#v", obj)
return
}
}
c.enqueueCRD(cast)
},
})
return c
}
func (c *crdRegistrationController) Run(workers int, stopCh <-chan struct{}) {
defer utilruntime.HandleCrash()
// make sure the work queue is shutdown which will trigger workers to end
defer c.queue.ShutDown()
klog.Infof("Starting crd-autoregister controller")
defer klog.Infof("Shutting down crd-autoregister controller")
// wait for your secondary caches to fill before starting your work
if !cache.WaitForNamedCacheSync("crd-autoregister", stopCh, c.crdSynced) {
return
}
// process each item in the list once
if crds, err := c.crdLister.List(labels.Everything()); err != nil {
utilruntime.HandleError(err)
} else {
for _, crd := range crds {
for _, version := range crd.Spec.Versions {
if err := c.syncHandler(schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name}); err != nil {
utilruntime.HandleError(err)
}
}
}
}
close(c.syncedInitialSet)
// start up your worker threads based on workers. Some controllers have multiple kinds of workers
for i := 0; i < workers; i++ {
// runWorker will loop until "something bad" happens. The .Until will then rekick the worker
// after one second
go wait.Until(c.runWorker, time.Second, stopCh)
}
// wait until we're told to stop
<-stopCh
}
// WaitForInitialSync blocks until the initial set of CRD resources has been processed
func (c *crdRegistrationController) WaitForInitialSync() {
<-c.syncedInitialSet
}
func (c *crdRegistrationController) runWorker() {
// hot loop until we're told to stop. processNextWorkItem will automatically wait until there's work
// available, so we don't worry about secondary waits
for c.processNextWorkItem() {
}
}
// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit.
func (c *crdRegistrationController) processNextWorkItem() bool {
// pull the next work item from queue. It should be a key we use to lookup something in a cache
key, quit := c.queue.Get()
if quit {
return false
}
// you always have to indicate to the queue that you've completed a piece of work
defer c.queue.Done(key)
// do your work on the key. This method will contains your "do stuff" logic
err := c.syncHandler(key.(schema.GroupVersion))
if err == nil {
// if you had no error, tell the queue to stop tracking history for your key. This will
// reset things like failure counts for per-item rate limiting
c.queue.Forget(key)
return true
}
// there was a failure so be sure to report it. This method allows for pluggable error handling
// which can be used for things like cluster-monitoring
utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err))
// since we failed, we should requeue the item to work on later. This method will add a backoff
// to avoid hotlooping on particular items (they're probably still not going to work right away)
// and overall controller protection (everything I've done is broken, this controller needs to
// calm down or it can starve other useful work) cases.
c.queue.AddRateLimited(key)
return true
}
func (c *crdRegistrationController) enqueueCRD(crd *apiextensionsv1.CustomResourceDefinition) {
for _, version := range crd.Spec.Versions {
c.queue.Add(schema.GroupVersion{Group: crd.Spec.Group, Version: version.Name})
}
}
func (c *crdRegistrationController) handleVersionUpdate(groupVersion schema.GroupVersion) error {
apiServiceName := groupVersion.Version + "." + groupVersion.Group
// check all CRDs. There shouldn't that many, but if we have problems later we can index them
crds, err := c.crdLister.List(labels.Everything())
if err != nil {
return err
}
for _, crd := range crds {
if crd.Spec.Group != groupVersion.Group {
continue
}
for _, version := range crd.Spec.Versions {
if version.Name != groupVersion.Version || !version.Served {
continue
}
c.apiServiceRegistration.AddAPIServiceToSync(&v1.APIService{
ObjectMeta: metav1.ObjectMeta{Name: apiServiceName},
Spec: v1.APIServiceSpec{
Group: groupVersion.Group,
Version: groupVersion.Version,
GroupPriorityMinimum: 1000, // CRDs should have relatively low priority
VersionPriority: 100, // CRDs will be sorted by kube-like versions like any other APIService with the same VersionPriority
},
})
return nil
}
}
c.apiServiceRegistration.RemoveAPIServiceToSync(apiServiceName)
return nil
}

View File

@ -19,8 +19,8 @@ import (
"github.com/grafana/grafana/pkg/plugins/config"
"github.com/grafana/grafana/pkg/plugins/pluginscdn"
accesscontrolmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/licensing"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginsettings"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"

View File

@ -25,8 +25,8 @@ import (
"github.com/prometheus/client_golang/prometheus/promhttp"
"github.com/grafana/grafana/pkg/services/anonymous"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/api/avatar"
"github.com/grafana/grafana/pkg/api/routing"

View File

@ -13,10 +13,10 @@ import (
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/middleware/requestmeta"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/util/errutil/errhttp"
"github.com/grafana/grafana/pkg/web"
)

View File

@ -15,10 +15,10 @@ import (
"github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
"github.com/grafana/grafana/pkg/middleware"
internalplaylist "github.com/grafana/grafana/pkg/registry/apis/playlist"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/util/errutil/errhttp"
"github.com/grafana/grafana/pkg/web"

View File

@ -1,51 +0,0 @@
# grafana aggregator
The `aggregator` command in this binary is our equivalent of what kube-apiserver does for aggregation using
the `kube-aggregator` pkg. Here, we enable only select controllers that are useful for aggregation in a Grafana
cloud context. In future, Grafana microservices (and even plugins) will run as separate API servers
hosting each their own APIs (with specific Group/Versions). The `aggregator` component here shall act similar to what
`kube-apiserver` does: doing healthchecks for `APIService` objects registered against it and acting as a proxy for
the specified `GroupVersion` therein.
## How to get started
1. Generate the PKI using `openssl` (for development purposes, we will use the CN of `system:masters`):
```shell
./hack/make-aggregator-pki.sh
```
2. Start the aggregator:
```shell
# This will generate the kubeconfig which you can use in the extension apiservers for
# enforcing delegate authnz under $PWD/data/grafana-apiserver/aggregator.kubeconfig
go run ./pkg/cmd/grafana aggregator --secure-port 8443 \
--proxy-client-cert-file $PWD/data/grafana-aggregator/client.crt \
--proxy-client-key-file $PWD/data/grafana-aggregator/client.key
```
3. Apply the manifests:
```shell
export KUBECONFIG=$PWD/data/grafana-apiserver/aggregator.kubeconfig
kubectl apply -k ./pkg/cmd/grafana/apiserver/deploy/aggregator-test
# SAMPLE OUTPUT
# apiservice.apiregistration.k8s.io/v0alpha1.example.grafana.app created
# externalname.service.grafana.app/example-apiserver created
kubectl get apiservice
# SAMPLE OUTPUT
# NAME SERVICE AVAILABLE AGE
# v0alpha1.example.grafana.app grafana/example-apiserver False (FailedDiscoveryCheck) 29m
```
4. In another tab, start the example microservice that will be aggregated by the parent apiserver:
```shell
go run ./pkg/cmd/grafana apiserver example.grafana.app \
--kubeconfig $PWD/data/grafana-aggregator/aggregator.kubeconfig \
--secure-port 7443 \
--client-ca-file=$PWD/data/grafana-aggregator/ca.crt
```
5. Check `APIService` again:
```shell
export KUBECONFIG=$PWD/data/grafana-apiserver/aggregator.kubeconfig
kubectl get apiservice
# SAMPLE OUTPUT
# NAME SERVICE AVAILABLE AGE
# v0alpha1.example.grafana.app grafana/example-apiserver True 30m
```

View File

@ -2,26 +2,13 @@ package apiserver
import (
"os"
"path"
"github.com/spf13/cobra"
genericapifilters "k8s.io/apiserver/pkg/endpoints/filters"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/util/notfoundhandler"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/component-base/cli"
"k8s.io/klog/v2"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
"github.com/grafana/grafana/pkg/aggregator"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
)
const (
aggregatorDataPath = "data"
defaultAggregatorEtcdPathPrefix = "/registry/grafana.aggregator"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
)
func newCommandStartExampleAPIServer(o *APIServerOptions, stopCh <-chan struct{}) *cobra.Command {
@ -74,97 +61,3 @@ func RunCLI() int {
return cli.Run(cmd)
}
func newCommandStartAggregator() *cobra.Command {
devAcknowledgementNotice := "The aggregator command is in heavy development. The entire setup is subject to change without notice"
cwd, err := os.Getwd()
if err != nil {
panic("could not determine current directory")
}
extraConfig := &aggregator.ExtraConfig{
DataPath: path.Join(cwd, aggregatorDataPath),
}
// Register standard k8s flags with the command line
recommendedOptions := options.NewRecommendedOptions(
defaultAggregatorEtcdPathPrefix,
aggregatorscheme.Codecs.LegacyCodec(), // codec is passed to etcd and hence not used
)
cmd := &cobra.Command{
Use: "aggregator",
Short: "Run the grafana aggregator",
Long: "Run a standalone kubernetes based aggregator server. " +
devAcknowledgementNotice,
Example: "grafana aggregator",
RunE: func(c *cobra.Command, args []string) error {
serverOptions, err := aggregator.NewAggregatorServerOptions(os.Stdout, os.Stderr, recommendedOptions, extraConfig)
serverOptions.Config.Complete()
if err != nil {
klog.Errorf("Could not create aggregator server options: %s", err)
os.Exit(1)
}
return run(serverOptions)
},
}
recommendedOptions.AddFlags(cmd.Flags())
extraConfig.AddFlags(cmd.Flags())
return cmd
}
func run(serverOptions *aggregator.AggregatorServerOptions) error {
if err := serverOptions.LoadAPIGroupBuilders(); err != nil {
klog.Errorf("Error loading prerequisite APIs: %s", err)
return err
}
notFoundHandler := notfoundhandler.New(serverOptions.Config.SharedConfig.Serializer, genericapifilters.NoMuxAndDiscoveryIncompleteKey)
apiExtensionsServer, err := serverOptions.Config.ApiExtensionsComplete.New(genericapiserver.NewEmptyDelegateWithCustomHandler(notFoundHandler))
if err != nil {
return err
}
aggregator, err := serverOptions.CreateAggregatorServer(apiExtensionsServer.GenericAPIServer, apiExtensionsServer.Informers)
if err != nil {
klog.Errorf("Error creating aggregator server: %s", err)
return err
}
// Install the API Group+version
err = grafanaapiserver.InstallAPIs(aggregator.GenericAPIServer, serverOptions.Config.Aggregator.GenericConfig.RESTOptionsGetter, serverOptions.Builders)
if err != nil {
klog.Errorf("Error installing apis: %s", err)
return err
}
if err := clientcmd.WriteToFile(
utils.FormatKubeConfig(aggregator.GenericAPIServer.LoopbackClientConfig),
path.Join(aggregatorDataPath, "grafana-aggregator", "aggregator.kubeconfig"),
); err != nil {
klog.Errorf("Error persisting aggregator.kubeconfig: %s", err)
return err
}
prepared, err := aggregator.PrepareRun()
if err != nil {
return err
}
stopCh := genericapiserver.SetupSignalHandler()
if err := prepared.Run(stopCh); err != nil {
return err
}
return nil
}
func RunCobraWrapper() int {
cmd := newCommandStartAggregator()
return cli.Run(cmd)
}

View File

@ -17,9 +17,10 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/query"
"github.com/grafana/grafana/pkg/registry/apis/query/runner"
"github.com/grafana/grafana/pkg/server"
grafanaAPIServer "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaAPIServer "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/setting"
)
@ -30,7 +31,7 @@ const (
// APIServerOptions contains the state for the apiserver
type APIServerOptions struct {
builders []grafanaAPIServer.APIGroupBuilder
builders []builder.APIGroupBuilder
RecommendedOptions *options.RecommendedOptions
AlternateDNS []string
@ -46,7 +47,7 @@ func newAPIServerOptions(out, errOut io.Writer) *APIServerOptions {
}
func (o *APIServerOptions) loadAPIGroupBuilders(args []string) error {
o.builders = []grafanaAPIServer.APIGroupBuilder{}
o.builders = []builder.APIGroupBuilder{}
for _, g := range args {
switch g {
// No dependencies for testing
@ -171,7 +172,7 @@ func (o *APIServerOptions) Config() (*genericapiserver.RecommendedConfig, error)
serverConfig.DisabledPostStartHooks = serverConfig.DisabledPostStartHooks.Insert("priority-and-fairness-config-consumer")
// Add OpenAPI specs for each group+version
err := grafanaAPIServer.SetupConfig(serverConfig, o.builders)
err := builder.SetupConfig(grafanaAPIServer.Scheme, serverConfig, o.builders)
return serverConfig, err
}
@ -199,7 +200,7 @@ func (o *APIServerOptions) RunAPIServer(config *genericapiserver.RecommendedConf
}
// Install the API Group+version
err = grafanaAPIServer.InstallAPIs(server, config.RESTOptionsGetter, o.builders)
err = builder.InstallAPIs(grafanaAPIServer.Scheme, grafanaAPIServer.Codecs, server, config.RESTOptionsGetter, o.builders, true)
if err != nil {
return err
}

View File

@ -46,18 +46,6 @@ func main() {
},
},
gsrv.ServerCommand(version, commit, enterpriseCommit, buildBranch, buildstamp),
{
// The kube-aggregator inspired grafana aggregator
Name: "aggregator",
Usage: "run grafana aggregator (experimental)",
// Skip parsing flags because the command line is actually managed by cobra
SkipFlagParsing: true,
Action: func(context *cli.Context) error {
// exit here because apiserver handles its own error output
os.Exit(apiserver.RunCobraWrapper())
return nil
},
},
},
CommandNotFound: cmdNotFound,
EnableBashCompletion: true,

View File

@ -11,6 +11,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/folders"
"github.com/grafana/grafana/pkg/registry/apis/playlist"
"github.com/grafana/grafana/pkg/registry/apis/query"
"github.com/grafana/grafana/pkg/registry/apis/service"
)
var (
@ -28,6 +29,7 @@ func ProvideRegistryServiceSink(
_ *featuretoggle.FeatureFlagAPIBuilder,
_ *datasource.DataSourceAPIBuilder,
_ *folders.FolderAPIBuilder,
_ *service.ServiceAPIBuilder,
_ *query.QueryAPIBuilder,
) *Service {
return &Service{}

View File

@ -14,9 +14,9 @@ import (
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/sqlstore/session"
)

View File

@ -7,8 +7,8 @@ import (
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/guardian"
)

View File

@ -14,7 +14,7 @@ import (
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/access"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
var (

View File

@ -19,19 +19,19 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/access"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
grafanaregistry "github.com/grafana/grafana/pkg/services/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/setting"
)
var _ grafanaapiserver.APIGroupBuilder = (*DashboardsAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*DashboardsAPIBuilder)(nil)
// This is used just so wire has something unique to return
type DashboardsAPIBuilder struct {
@ -47,7 +47,7 @@ type DashboardsAPIBuilder struct {
}
func RegisterAPIService(cfg *setting.Cfg, features featuremgmt.FeatureToggles,
apiregistration grafanaapiserver.APIRegistrar,
apiregistration builder.APIRegistrar,
dashboardService dashboards.DashboardService,
dashboardVersionService dashver.Service,
accessControl accesscontrol.AccessControl,
@ -113,6 +113,7 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
dualWrite bool,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(v0alpha1.GROUP, scheme, metav1.ParameterCodec, codecs)
@ -171,7 +172,7 @@ func (b *DashboardsAPIBuilder) GetAPIGroupInfo(
}
// Dual writes if a RESTOptionsGetter is provided
if optsGetter != nil {
if dualWrite && optsGetter != nil {
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: grafanaregistry.GetAttrs}
if err := store.CompleteWithOptions(options); err != nil {
return nil, err
@ -195,6 +196,6 @@ func (b *DashboardsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefiniti
return v0alpha1.GetOpenAPIDefinitions
}
func (b *DashboardsAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *DashboardsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil // no custom API routes
}

View File

@ -11,9 +11,9 @@ import (
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/auth/identity"
dashboardssvc "github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/guardian"
)

View File

@ -13,8 +13,8 @@ import (
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
dashver "github.com/grafana/grafana/pkg/services/dashboardversion"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
)
type VersionsREST struct {

View File

@ -10,7 +10,7 @@ import (
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/access"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
var (

View File

@ -9,9 +9,9 @@ import (
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
)

View File

@ -0,0 +1,148 @@
package datasource
import (
"context"
"github.com/grafana/grafana-plugin-sdk-go/backend"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/datasources"
)
type QuerierFactoryFunc func(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error)
type QuerierProvider interface {
Querier(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error)
}
type DefaultQuerierProvider struct {
factory QuerierFactoryFunc
}
func ProvideDefaultQuerierProvider(pluginClient plugins.Client, dsService datasources.DataSourceService,
dsCache datasources.CacheService) *DefaultQuerierProvider {
return NewQuerierProvider(func(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error) {
return NewDefaultQuerier(ri, pj, pluginClient, dsService, dsCache), nil
})
}
func NewQuerierProvider(factory QuerierFactoryFunc) *DefaultQuerierProvider {
return &DefaultQuerierProvider{
factory: factory,
}
}
func (p *DefaultQuerierProvider) Querier(ctx context.Context, ri common.ResourceInfo, pj plugins.JSONData) (Querier, error) {
return p.factory(ctx, ri, pj)
}
// Querier is the interface that wraps the Query method.
type Querier interface {
// Query runs the query on behalf of the user in context.
Query(ctx context.Context, query *backend.QueryDataRequest) (*backend.QueryDataResponse, error)
// Health checks the health of the plugin.
Health(ctx context.Context, req *backend.CheckHealthRequest) (*backend.CheckHealthResult, error)
// Resource gets a resource plugin.
Resource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error
// Datasource gets all data source plugins (with elevated permissions).
Datasource(ctx context.Context, name string) (*v0alpha1.DataSourceConnection, error)
// Datasources lists all data sources (with elevated permissions).
Datasources(ctx context.Context) (*v0alpha1.DataSourceConnectionList, error)
}
type DefaultQuerier struct {
connectionResourceInfo common.ResourceInfo
pluginJSON plugins.JSONData
pluginClient plugins.Client
dsService datasources.DataSourceService
dsCache datasources.CacheService
}
func NewDefaultQuerier(
connectionResourceInfo common.ResourceInfo,
pluginJSON plugins.JSONData,
pluginClient plugins.Client,
dsService datasources.DataSourceService,
dsCache datasources.CacheService,
) *DefaultQuerier {
return &DefaultQuerier{
connectionResourceInfo: connectionResourceInfo,
pluginJSON: pluginJSON,
pluginClient: pluginClient,
dsService: dsService,
dsCache: dsCache,
}
}
func (q *DefaultQuerier) Query(ctx context.Context, query *backend.QueryDataRequest) (*backend.QueryDataResponse, error) {
_, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
return q.pluginClient.QueryData(ctx, query)
}
func (q *DefaultQuerier) Resource(ctx context.Context, req *backend.CallResourceRequest, sender backend.CallResourceResponseSender) error {
_, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return err
}
return q.pluginClient.CallResource(ctx, req, sender)
}
func (q *DefaultQuerier) Health(ctx context.Context, query *backend.CheckHealthRequest) (*backend.CheckHealthResult, error) {
_, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
return q.pluginClient.CheckHealth(ctx, query)
}
func (q *DefaultQuerier) Datasource(ctx context.Context, name string) (*v0alpha1.DataSourceConnection, error) {
info, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
user, err := appcontext.User(ctx)
if err != nil {
return nil, err
}
ds, err := q.dsCache.GetDatasourceByUID(ctx, name, user, false)
if err != nil {
return nil, err
}
return asConnection(ds, info.Value)
}
func (q *DefaultQuerier) Datasources(ctx context.Context) (*v0alpha1.DataSourceConnectionList, error) {
info, err := request.NamespaceInfoFrom(ctx, true)
if err != nil {
return nil, err
}
ds, err := q.dsService.GetDataSourcesByType(ctx, &datasources.GetDataSourcesByTypeQuery{
OrgID: info.OrgID,
Type: q.pluginJSON.ID,
})
if err != nil {
return nil, err
}
return asConnectionList(q.connectionResourceInfo.TypeMeta(), ds, info.Value)
}
func asConnectionList(typeMeta metav1.TypeMeta, dss []*datasources.DataSource, ns string) (*v0alpha1.DataSourceConnectionList, error) {
result := &v0alpha1.DataSourceConnectionList{
Items: []v0alpha1.DataSourceConnection{},
}
for _, ds := range dss {
v, _ := asConnection(ds, ns)
result.Items = append(result.Items, *v)
}
return result, nil
}

View File

@ -23,13 +23,13 @@ import (
query "github.com/grafana/grafana/pkg/apis/query/v0alpha1"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
)
var _ grafanaapiserver.APIGroupBuilder = (*DataSourceAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*DataSourceAPIBuilder)(nil)
// DataSourceAPIBuilder is used just so wire has something unique to return
type DataSourceAPIBuilder struct {
@ -44,7 +44,7 @@ type DataSourceAPIBuilder struct {
func RegisterAPIService(
features featuremgmt.FeatureToggles,
apiRegistrar grafanaapiserver.APIRegistrar,
apiRegistrar builder.APIRegistrar,
pluginClient plugins.Client, // access to everything
datasources PluginDatasourceProvider,
contextProvider PluginContextWrapper,
@ -151,6 +151,7 @@ func (b *DataSourceAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
_ generic.RESTOptionsGetter,
_ bool,
) (*genericapiserver.APIGroupInfo, error) {
storage := map[string]rest.Storage{}
@ -213,6 +214,6 @@ func (b *DataSourceAPIBuilder) GetOpenAPIDefinitions() openapi.GetOpenAPIDefinit
}
// Register additional routes with the server
func (b *DataSourceAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *DataSourceAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil
}

View File

@ -10,8 +10,8 @@ import (
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/accesscontrol/actest"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/setting"
testdatasource "github.com/grafana/grafana/pkg/tsdb/grafana-testdata-datasource"
)

View File

@ -13,8 +13,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
grafanaregistry "github.com/grafana/grafana/pkg/services/apiserver/registry/generic"
)
var (

View File

@ -22,11 +22,11 @@ import (
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
)
var _ grafanaapiserver.APIGroupBuilder = (*TestingAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*TestingAPIBuilder)(nil)
// This is used just so wire has something unique to return
type TestingAPIBuilder struct {
@ -40,7 +40,7 @@ func NewTestingAPIBuilder() *TestingAPIBuilder {
}
}
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder {
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar) *TestingAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil // skip registration unless opting into experimental apis
}
@ -84,7 +84,8 @@ func (b *TestingAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
func (b *TestingAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
_ generic.RESTOptionsGetter,
_ bool,
) (*genericapiserver.APIGroupInfo, error) {
b.codecs = codecs
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(b.gv.Group, scheme, metav1.ParameterCodec, codecs)
@ -102,9 +103,9 @@ func (b *TestingAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions
}
// Register additional routes with the server
func (b *TestingAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
return &grafanaapiserver.APIRoutes{
Root: []grafanaapiserver.APIRouteHandler{
func (b *TestingAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return &builder.APIRoutes{
Root: []builder.APIRouteHandler{
{
Path: "aaa",
Spec: &spec3.PathProps{
@ -166,7 +167,7 @@ func (b *TestingAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
},
},
},
Namespace: []grafanaapiserver.APIRouteHandler{
Namespace: []builder.APIRouteHandler{
{
Path: "ccc",
Spec: &spec3.PathProps{

View File

@ -12,7 +12,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
grafanaregistry "github.com/grafana/grafana/pkg/services/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/setting"
)

View File

@ -10,7 +10,7 @@ import (
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
type dummySubresourceREST struct{}

View File

@ -12,8 +12,8 @@ import (
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
)
var (

View File

@ -14,11 +14,11 @@ import (
"k8s.io/kube-openapi/pkg/validation/spec"
"github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
)
var _ grafanaapiserver.APIGroupBuilder = (*FeatureFlagAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*FeatureFlagAPIBuilder)(nil)
var gv = v0alpha1.SchemeGroupVersion
@ -32,7 +32,7 @@ func NewFeatureFlagAPIBuilder(features *featuremgmt.FeatureManager) *FeatureFlag
}
func RegisterAPIService(features *featuremgmt.FeatureManager,
apiregistration grafanaapiserver.APIRegistrar,
apiregistration builder.APIRegistrar,
) *FeatureFlagAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil // skip registration unless opting into experimental apis
@ -78,7 +78,8 @@ func (b *FeatureFlagAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
func (b *FeatureFlagAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
_ generic.RESTOptionsGetter,
_ bool,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(v0alpha1.GROUP, scheme, metav1.ParameterCodec, codecs)
@ -102,13 +103,13 @@ func (b *FeatureFlagAPIBuilder) GetAuthorizer() authorizer.Authorizer {
}
// Register additional routes with the server
func (b *FeatureFlagAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *FeatureFlagAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
defs := v0alpha1.GetOpenAPIDefinitions(func(path string) spec.Ref { return spec.Ref{} })
stateSchema := defs["github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1.ResolvedToggleState"].Schema
tags := []string{"Editor"}
return &grafanaapiserver.APIRoutes{
Root: []grafanaapiserver.APIRouteHandler{
return &builder.APIRoutes{
Root: []builder.APIRouteHandler{
{
Path: "current",
Spec: &spec3.PathProps{

View File

@ -11,8 +11,8 @@ import (
common "github.com/grafana/grafana/pkg/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/apis/featuretoggle/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
)
var (

View File

@ -6,9 +6,9 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
)
func convertToK8sResource(v *folder.Folder, namespacer request.NamespaceMapper) *v0alpha1.Folder {

View File

@ -13,11 +13,11 @@ import (
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/storage/entity"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/storage/entity"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/util"
)

View File

@ -14,16 +14,16 @@ import (
common "k8s.io/kube-openapi/pkg/common"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
grafanarest "github.com/grafana/grafana/pkg/services/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/setting"
)
var _ grafanaapiserver.APIGroupBuilder = (*FolderAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*FolderAPIBuilder)(nil)
var resourceInfo = v0alpha1.FolderResourceInfo
@ -37,7 +37,7 @@ type FolderAPIBuilder struct {
func RegisterAPIService(cfg *setting.Cfg,
features *featuremgmt.FeatureManager,
apiregistration grafanaapiserver.APIRegistrar,
apiregistration builder.APIRegistrar,
folderSvc folder.Service,
) *FolderAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
@ -89,6 +89,7 @@ func (b *FolderAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
dualWrite bool,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(v0alpha1.GROUP, scheme, metav1.ParameterCodec, codecs)
@ -122,7 +123,7 @@ func (b *FolderAPIBuilder) GetAPIGroupInfo(
storage[resourceInfo.StoragePath("children")] = &subChildrenREST{b.folderSvc}
// enable dual writes if a RESTOptionsGetter is provided
if optsGetter != nil {
if dualWrite && optsGetter != nil {
store, err := newStorage(scheme, optsGetter, legacyStore)
if err != nil {
return nil, err
@ -138,7 +139,7 @@ func (b *FolderAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions
return v0alpha1.GetOpenAPIDefinitions
}
func (b *FolderAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *FolderAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil // no custom API routes
}

View File

@ -6,8 +6,8 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
grafanaregistry "github.com/grafana/grafana/pkg/services/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/apiserver/rest"
)
var _ grafanarest.Storage = (*storage)(nil)

View File

@ -9,8 +9,8 @@ import (
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
)
type subChildrenREST struct {

View File

@ -8,8 +8,8 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
"github.com/grafana/grafana/pkg/apis/folder/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
)
type subParentsREST struct {

View File

@ -11,8 +11,8 @@ import (
"k8s.io/apimachinery/pkg/types"
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
playlistsvc "github.com/grafana/grafana/pkg/services/playlist"
)

View File

@ -6,7 +6,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/playlist"
)

View File

@ -11,7 +11,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest"
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
playlistsvc "github.com/grafana/grafana/pkg/services/playlist"
)

View File

@ -15,15 +15,15 @@ import (
common "k8s.io/kube-openapi/pkg/common"
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
grafanarest "github.com/grafana/grafana/pkg/services/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
playlistsvc "github.com/grafana/grafana/pkg/services/playlist"
"github.com/grafana/grafana/pkg/setting"
)
var _ grafanaapiserver.APIGroupBuilder = (*PlaylistAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*PlaylistAPIBuilder)(nil)
// This is used just so wire has something unique to return
type PlaylistAPIBuilder struct {
@ -33,7 +33,7 @@ type PlaylistAPIBuilder struct {
}
func RegisterAPIService(p playlistsvc.Service,
apiregistration grafanaapiserver.APIRegistrar,
apiregistration builder.APIRegistrar,
cfg *setting.Cfg,
) *PlaylistAPIBuilder {
builder := &PlaylistAPIBuilder{
@ -79,6 +79,7 @@ func (b *PlaylistAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
dualWrite bool,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(playlist.GROUP, scheme, metav1.ParameterCodec, codecs)
storage := map[string]rest.Storage{}
@ -128,7 +129,7 @@ func (b *PlaylistAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinition
return playlist.GetOpenAPIDefinitions
}
func (b *PlaylistAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *PlaylistAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil // no custom API routes
}

View File

@ -6,8 +6,8 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
grafanaregistry "github.com/grafana/grafana/pkg/services/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/apiserver/rest"
)
var _ grafanarest.Storage = (*storage)(nil)

View File

@ -21,14 +21,14 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/registry/apis/query/runner"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
"github.com/grafana/grafana/pkg/services/pluginsintegration/pluginstore"
)
var _ grafanaapiserver.APIGroupBuilder = (*QueryAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*QueryAPIBuilder)(nil)
type QueryAPIBuilder struct {
log log.Logger
@ -54,7 +54,7 @@ func NewQueryAPIBuilder(features featuremgmt.FeatureToggles,
}
func RegisterAPIService(features featuremgmt.FeatureToggles,
apiregistration grafanaapiserver.APIRegistrar,
apiregistration builder.APIRegistrar,
dataSourcesService datasources.DataSourceService,
pluginStore pluginstore.Store,
accessControl accesscontrol.AccessControl,
@ -106,6 +106,7 @@ func (b *QueryAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
optsGetter generic.RESTOptionsGetter,
_ bool,
) (*genericapiserver.APIGroupInfo, error) {
gv := v0alpha1.SchemeGroupVersion
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(gv.Group, scheme, metav1.ParameterCodec, codecs)
@ -124,7 +125,7 @@ func (b *QueryAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
}
// Register additional routes with the server
func (b *QueryAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *QueryAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
defs := v0alpha1.GetOpenAPIDefinitions(func(path string) spec.Ref { return spec.Ref{} })
querySchema := defs["github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryRequest"].Schema
responseSchema := defs["github.com/grafana/grafana/pkg/apis/query/v0alpha1.QueryDataResponse"].Schema
@ -167,9 +168,9 @@ func (b *QueryAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
"to": "1704914981544"
}`), &randomWalkTable)
return &grafanaapiserver.APIRoutes{
Root: []grafanaapiserver.APIRouteHandler{},
Namespace: []grafanaapiserver.APIRouteHandler{
return &builder.APIRoutes{
Root: []builder.APIRouteHandler{},
Namespace: []builder.APIRouteHandler{
{
Path: "query",
Spec: &spec3.PathProps{

View File

@ -12,11 +12,11 @@ import (
"k8s.io/kube-openapi/pkg/common"
service "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
)
var _ grafanaapiserver.APIGroupBuilder = (*ServiceAPIBuilder)(nil)
var _ builder.APIGroupBuilder = (*ServiceAPIBuilder)(nil)
// This is used just so wire has something unique to return
type ServiceAPIBuilder struct{}
@ -25,10 +25,7 @@ func NewServiceAPIBuilder() *ServiceAPIBuilder {
return &ServiceAPIBuilder{}
}
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *ServiceAPIBuilder {
if !features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
return nil // skip registration unless opting into experimental apis
}
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration builder.APIRegistrar) *ServiceAPIBuilder {
builder := NewServiceAPIBuilder()
apiregistration.RegisterAPI(NewServiceAPIBuilder())
return builder
@ -64,6 +61,7 @@ func (b *ServiceAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory,
optsGetter generic.RESTOptionsGetter,
_ bool,
) (*genericapiserver.APIGroupInfo, error) {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(service.GROUP, scheme, metav1.ParameterCodec, codecs)
@ -83,6 +81,6 @@ func (b *ServiceAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions
}
// Register additional routes with the server
func (b *ServiceAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
func (b *ServiceAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil
}

View File

@ -10,9 +10,9 @@ import (
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
service "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
grafanaregistry "github.com/grafana/grafana/pkg/services/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/services/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
)
var _ grafanarest.Storage = (*storage)(nil)

View File

@ -10,6 +10,7 @@ import (
"github.com/grafana/grafana/pkg/registry/apis/folders"
"github.com/grafana/grafana/pkg/registry/apis/playlist"
"github.com/grafana/grafana/pkg/registry/apis/query"
"github.com/grafana/grafana/pkg/registry/apis/service"
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
)
@ -28,5 +29,6 @@ var WireSet = wire.NewSet(
featuretoggle.RegisterAPIService,
datasource.RegisterAPIService,
folders.RegisterAPIService,
service.RegisterAPIService,
query.RegisterAPIService,
)

View File

@ -11,10 +11,10 @@ import (
apiregistry "github.com/grafana/grafana/pkg/registry/apis"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/cleanup"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grpcserver"
"github.com/grafana/grafana/pkg/services/guardian"
ldapapi "github.com/grafana/grafana/pkg/services/ldap/api"

View File

@ -44,6 +44,7 @@ import (
"github.com/grafana/grafana/pkg/services/annotations/annotationsimpl"
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl/anonstore"
"github.com/grafana/grafana/pkg/services/apikey/apikeyimpl"
grafanaapiserver "github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/auth/idimpl"
"github.com/grafana/grafana/pkg/services/auth/jwt"
@ -72,7 +73,6 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grpcserver"
grpccontext "github.com/grafana/grafana/pkg/services/grpcserver/context"
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"

View File

@ -0,0 +1,285 @@
// SPDX-License-Identifier: AGPL-3.0-only
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/aggregator.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/server.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/pkg/controlplane/apiserver/apiextensions.go
// Provenance-includes-license: Apache-2.0
// Provenance-includes-copyright: The Kubernetes Authors.
package aggregator
import (
"crypto/tls"
"fmt"
"net/http"
"strings"
"sync"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
utilnet "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apimachinery/pkg/util/sets"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/healthz"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1"
v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper"
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
apiregistrationclientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset"
apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1"
apiregistrationInformers "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1"
"k8s.io/kube-aggregator/pkg/controllers/autoregister"
serviceclientset "github.com/grafana/grafana/pkg/generated/clientset/versioned"
informersv0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions"
"github.com/grafana/grafana/pkg/services/apiserver/options"
)
func CreateAggregatorConfig(commandOptions *options.Options, sharedConfig genericapiserver.RecommendedConfig) (*aggregatorapiserver.Config, informersv0alpha1.SharedInformerFactory, error) {
// Create a fake clientset and informers for the k8s v1 API group.
// These are not used in grafana's aggregator because v1 APIs are not available.
fakev1Informers := informers.NewSharedInformerFactory(fake.NewSimpleClientset(), 10*time.Minute)
serviceClient, err := serviceclientset.NewForConfig(sharedConfig.LoopbackClientConfig)
if err != nil {
return nil, nil, err
}
sharedInformerFactory := informersv0alpha1.NewSharedInformerFactory(
serviceClient,
5*time.Minute, // this is effectively used as a refresh interval right now. Might want to do something nicer later on.
)
serviceResolver := NewExternalNameResolver(sharedInformerFactory.Service().V0alpha1().ExternalNames().Lister())
aggregatorConfig := &aggregatorapiserver.Config{
GenericConfig: &genericapiserver.RecommendedConfig{
Config: sharedConfig.Config,
SharedInformerFactory: fakev1Informers,
ClientConfig: sharedConfig.LoopbackClientConfig,
},
ExtraConfig: aggregatorapiserver.ExtraConfig{
ProxyClientCertFile: commandOptions.AggregatorOptions.ProxyClientCertFile,
ProxyClientKeyFile: commandOptions.AggregatorOptions.ProxyClientKeyFile,
// NOTE: while ProxyTransport can be skipped in the configuration, it allows honoring
// DISABLE_HTTP2, HTTPS_PROXY and NO_PROXY env vars as needed
ProxyTransport: createProxyTransport(),
ServiceResolver: serviceResolver,
},
}
if err := commandOptions.AggregatorOptions.ApplyTo(aggregatorConfig, commandOptions.RecommendedOptions.Etcd, commandOptions.StorageOptions.DataPath); err != nil {
return nil, nil, err
}
return aggregatorConfig, sharedInformerFactory, nil
}
func CreateAggregatorServer(aggregatorConfig *aggregatorapiserver.Config, sharedInformerFactory informersv0alpha1.SharedInformerFactory, delegateAPIServer genericapiserver.DelegationTarget) (*aggregatorapiserver.APIAggregator, error) {
completedConfig := aggregatorConfig.Complete()
aggregatorServer, err := completedConfig.NewWithDelegate(delegateAPIServer)
if err != nil {
return nil, err
}
// create controllers for auto-registration
apiRegistrationClient, err := apiregistrationclient.NewForConfig(completedConfig.GenericConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
autoRegistrationController := autoregister.NewAutoRegisterController(aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(), apiRegistrationClient)
apiServices := apiServicesToRegister(delegateAPIServer, autoRegistrationController)
// Imbue all builtin group-priorities onto the aggregated discovery
if completedConfig.GenericConfig.AggregatedDiscoveryGroupManager != nil {
for gv, entry := range APIVersionPriorities {
completedConfig.GenericConfig.AggregatedDiscoveryGroupManager.SetGroupVersionPriority(metav1.GroupVersion(gv), int(entry.Group), int(entry.Version))
}
}
err = aggregatorServer.GenericAPIServer.AddPostStartHook("grafana-apiserver-autoregistration", func(context genericapiserver.PostStartHookContext) error {
go func() {
autoRegistrationController.Run(5, context.StopCh)
}()
return nil
})
if err != nil {
return nil, err
}
err = aggregatorServer.GenericAPIServer.AddBootSequenceHealthChecks(
makeAPIServiceAvailableHealthCheck(
"autoregister-completion",
apiServices,
aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(),
),
)
if err != nil {
return nil, err
}
apiregistrationClient, err := apiregistrationclientset.NewForConfig(completedConfig.GenericConfig.LoopbackClientConfig)
if err != nil {
return nil, err
}
availableController, err := NewAvailableConditionController(
aggregatorServer.APIRegistrationInformers.Apiregistration().V1().APIServices(),
sharedInformerFactory.Service().V0alpha1().ExternalNames(),
apiregistrationClient.ApiregistrationV1(),
nil,
(func() ([]byte, []byte))(nil),
completedConfig.ExtraConfig.ServiceResolver,
)
if err != nil {
return nil, err
}
aggregatorServer.GenericAPIServer.AddPostStartHookOrDie("apiservice-status-override-available-controller", func(context genericapiserver.PostStartHookContext) error {
// if we end up blocking for long periods of time, we may need to increase workers.
go availableController.Run(5, context.StopCh)
return nil
})
aggregatorServer.GenericAPIServer.AddPostStartHookOrDie("start-grafana-aggregator-informers", func(context genericapiserver.PostStartHookContext) error {
sharedInformerFactory.Start(context.StopCh)
aggregatorServer.APIRegistrationInformers.Start(context.StopCh)
return nil
})
return aggregatorServer, nil
}
func makeAPIService(gv schema.GroupVersion) *v1.APIService {
apiServicePriority, ok := APIVersionPriorities[gv]
if !ok {
// if we aren't found, then we shouldn't register ourselves because it could result in a CRD group version
// being permanently stuck in the APIServices list.
klog.Infof("Skipping APIService creation for %v", gv)
return nil
}
return &v1.APIService{
ObjectMeta: metav1.ObjectMeta{Name: gv.Version + "." + gv.Group},
Spec: v1.APIServiceSpec{
Group: gv.Group,
Version: gv.Version,
GroupPriorityMinimum: apiServicePriority.Group,
VersionPriority: apiServicePriority.Version,
},
}
}
// makeAPIServiceAvailableHealthCheck returns a healthz check that returns healthy
// once all of the specified services have been observed to be available at least once.
func makeAPIServiceAvailableHealthCheck(name string, apiServices []*v1.APIService, apiServiceInformer apiregistrationInformers.APIServiceInformer) healthz.HealthChecker {
// Track the auto-registered API services that have not been observed to be available yet
pendingServiceNamesLock := &sync.RWMutex{}
pendingServiceNames := sets.NewString()
for _, service := range apiServices {
pendingServiceNames.Insert(service.Name)
}
// When an APIService in the list is seen as available, remove it from the pending list
handleAPIServiceChange := func(service *v1.APIService) {
pendingServiceNamesLock.Lock()
defer pendingServiceNamesLock.Unlock()
if !pendingServiceNames.Has(service.Name) {
return
}
if v1helper.IsAPIServiceConditionTrue(service, v1.Available) {
pendingServiceNames.Delete(service.Name)
}
}
// Watch add/update events for APIServices
_, _ = apiServiceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) { handleAPIServiceChange(obj.(*v1.APIService)) },
UpdateFunc: func(old, new interface{}) { handleAPIServiceChange(new.(*v1.APIService)) },
})
// Don't return healthy until the pending list is empty
return healthz.NamedCheck(name, func(r *http.Request) error {
pendingServiceNamesLock.RLock()
defer pendingServiceNamesLock.RUnlock()
if pendingServiceNames.Len() > 0 {
return fmt.Errorf("missing APIService: %v", pendingServiceNames.List())
}
return nil
})
}
// Priority defines group Priority that is used in discovery. This controls
// group position in the kubectl output.
type Priority struct {
// Group indicates the order of the Group relative to other groups.
Group int32
// Version indicates the relative order of the Version inside of its group.
Version int32
}
// APIVersionPriorities are the proper way to resolve this letting the aggregator know the desired group and version-within-group order of the underlying servers
// is to refactor the genericapiserver.DelegationTarget to include a list of priorities based on which APIs were installed.
// This requires the APIGroupInfo struct to evolve and include the concept of priorities and to avoid mistakes, the core storage map there needs to be updated.
// That ripples out every bit as far as you'd expect, so for 1.7 we'll include the list here instead of being built up during storage.
var APIVersionPriorities = map[schema.GroupVersion]Priority{
{Group: "", Version: "v1"}: {Group: 18000, Version: 1},
// to my knowledge, nothing below here collides
{Group: "admissionregistration.k8s.io", Version: "v1"}: {Group: 16700, Version: 15},
{Group: "admissionregistration.k8s.io", Version: "v1beta1"}: {Group: 16700, Version: 12},
{Group: "admissionregistration.k8s.io", Version: "v1alpha1"}: {Group: 16700, Version: 9},
// Append a new group to the end of the list if unsure.
// You can use min(existing group)-100 as the initial value for a group.
// Version can be set to 9 (to have space around) for a new group.
}
func apiServicesToRegister(delegateAPIServer genericapiserver.DelegationTarget, registration autoregister.AutoAPIServiceRegistration) []*v1.APIService {
apiServices := []*v1.APIService{}
for _, curr := range delegateAPIServer.ListedPaths() {
if curr == "/api/v1" {
apiService := makeAPIService(schema.GroupVersion{Group: "", Version: "v1"})
registration.AddAPIServiceToSyncOnStart(apiService)
apiServices = append(apiServices, apiService)
continue
}
if !strings.HasPrefix(curr, "/apis/") {
continue
}
// this comes back in a list that looks like /apis/rbac.authorization.k8s.io/v1alpha1
tokens := strings.Split(curr, "/")
if len(tokens) != 4 {
continue
}
apiService := makeAPIService(schema.GroupVersion{Group: tokens[2], Version: tokens[3]})
if apiService == nil {
continue
}
registration.AddAPIServiceToSyncOnStart(apiService)
apiServices = append(apiServices, apiService)
}
return apiServices
}
// NOTE: below function imported from https://github.com/kubernetes/kubernetes/blob/master/cmd/kube-apiserver/app/server.go#L197
// createProxyTransport creates the dialer infrastructure to connect to the api servers.
func createProxyTransport() *http.Transport {
// NOTE: We don't set proxyDialerFn but the below SetTransportDefaults will
// See https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apimachinery/pkg/util/net/http.go#L109
var proxyDialerFn utilnet.DialFunc
// Proxying to services is IP-based... don't expect to be able to verify the hostname
proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true}
proxyTransport := utilnet.SetTransportDefaults(&http.Transport{
DialContext: proxyDialerFn,
TLSClientConfig: proxyTLSClientConfig,
})
return proxyTransport
}

View File

@ -8,7 +8,7 @@ import (
"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"
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/org"
)

View File

@ -8,7 +8,7 @@ import (
"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"
grafanarequest "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/setting"
)

View File

@ -1,4 +1,4 @@
package grafanaapiserver
package builder
import (
"net/http"
@ -27,6 +27,7 @@ type APIGroupBuilder interface {
scheme *runtime.Scheme,
codecs serializer.CodecFactory,
optsGetter generic.RESTOptionsGetter,
dualWrite bool,
) (*genericapiserver.APIGroupInfo, error)
// Get OpenAPI definitions
@ -57,3 +58,7 @@ type APIRoutes struct {
// Namespace handlers are mounted under the namespace
Namespace []APIRouteHandler
}
type APIRegistrar interface {
RegisterAPI(builder APIGroupBuilder)
}

View File

@ -1,34 +1,45 @@
package grafanaapiserver
package builder
import (
"fmt"
"net/http"
goruntime "runtime"
"runtime/debug"
"strconv"
"strings"
"time"
"golang.org/x/mod/semver"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apimachinery/pkg/version"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/apiserver/pkg/registry/generic"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/util/openapi"
"k8s.io/client-go/kubernetes/scheme"
k8sscheme "k8s.io/client-go/kubernetes/scheme"
"k8s.io/kube-openapi/pkg/common"
"github.com/grafana/grafana/pkg/setting"
)
func SetupConfig(serverConfig *genericapiserver.RecommendedConfig, builders []APIGroupBuilder) error {
func SetupConfig(
scheme *runtime.Scheme,
serverConfig *genericapiserver.RecommendedConfig,
builders []APIGroupBuilder,
) error {
defsGetter := GetOpenAPIDefinitions(builders)
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme))
serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
openapinamer.NewDefinitionNamer(scheme, k8sscheme.Scheme))
// Add the custom routes to service discovery
serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(builders)
serverConfig.OpenAPIV3Config.GetOperationIDAndTagsFromRoute = func(r common.Route) (string, []string, error) {
tags := []string{}
prop, ok := r.Metadata()["x-kubernetes-group-version-kind"]
@ -41,9 +52,6 @@ func SetupConfig(serverConfig *genericapiserver.RecommendedConfig, builders []AP
return r.OperationName(), tags, nil
}
// Add the custom routes to service discovery
serverConfig.OpenAPIV3Config.PostProcessSpec = getOpenAPIPostProcessor(builders)
// Set the swagger build versions
serverConfig.OpenAPIConfig.Info.Version = setting.BuildVersion
serverConfig.OpenAPIV3Config.Info.Version = setting.BuildVersion
@ -82,12 +90,16 @@ func SetupConfig(serverConfig *genericapiserver.RecommendedConfig, builders []AP
return nil
}
func InstallAPIs(server *genericapiserver.GenericAPIServer,
func InstallAPIs(
scheme *runtime.Scheme,
codecs serializer.CodecFactory,
server *genericapiserver.GenericAPIServer,
optsGetter generic.RESTOptionsGetter,
builders []APIGroupBuilder,
dualWrite bool,
) error {
for _, b := range builders {
g, err := b.GetAPIGroupInfo(Scheme, Codecs, optsGetter)
g, err := b.GetAPIGroupInfo(scheme, codecs, optsGetter, dualWrite)
if err != nil {
return err
}
@ -101,3 +113,33 @@ func InstallAPIs(server *genericapiserver.GenericAPIServer,
}
return nil
}
// find the k8s version according to build info
func getK8sApiserverVersion() (string, error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
return "", fmt.Errorf("debug.ReadBuildInfo() failed")
}
if len(bi.Deps) == 0 {
return "v?.?", nil // this is normal while debugging
}
for _, dep := range bi.Deps {
if dep.Path == "k8s.io/apiserver" {
if !semver.IsValid(dep.Version) {
return "", fmt.Errorf("invalid semantic version for k8s.io/apiserver")
}
// v0 => v1
majorVersion := strings.TrimPrefix(semver.Major(dep.Version), "v")
majorInt, err := strconv.Atoi(majorVersion)
if err != nil {
return "", fmt.Errorf("could not convert majorVersion to int. majorVersion: %s", majorVersion)
}
newMajor := fmt.Sprintf("v%d", majorInt+1)
return strings.Replace(dep.Version, semver.Major(dep.Version), newMajor, 1), nil
}
}
return "", fmt.Errorf("could not find k8s.io/apiserver in build info")
}

View File

@ -1,4 +1,4 @@
package grafanaapiserver
package builder
import (
"maps"
@ -15,8 +15,8 @@ func GetOpenAPIDefinitions(builders []APIGroupBuilder) common.GetOpenAPIDefiniti
return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
defs := getStandardOpenAPIDefinitions(ref)
maps.Copy(defs, v0alpha1.GetOpenAPIDefinitions(ref)) // common grafana apis
for _, builder := range builders {
g := builder.GetOpenAPIDefinitions()
for _, b := range builders {
g := b.GetOpenAPIDefinitions()
if g != nil {
out := g(ref)
maps.Copy(defs, out)

View File

@ -1,4 +1,4 @@
package grafanaapiserver
package builder
import (
"fmt"
@ -125,9 +125,9 @@ func getOpenAPIPostProcessor(builders []APIGroupBuilder) func(*spec3.OpenAPI) (*
if s.Paths == nil {
return s, nil
}
for _, builder := range builders {
routes := builder.GetAPIRoutes()
gv := builder.GetGroupVersion()
for _, b := range builders {
routes := b.GetAPIRoutes()
gv := b.GetGroupVersion()
prefix := "/apis/" + gv.String() + "/"
if s.Paths.Paths[prefix] != nil {
copy := spec3.OpenAPI{

View File

@ -0,0 +1,48 @@
package apiserver
import (
"fmt"
"net"
"path/filepath"
"strconv"
"github.com/grafana/grafana/pkg/services/apiserver/options"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
func applyGrafanaConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles, o *options.Options) {
defaultLogLevel := 0
ip := net.ParseIP(cfg.HTTPAddr)
apiURL := cfg.AppURL
port, err := strconv.Atoi(cfg.HTTPPort)
if err != nil {
port = 3000
}
if cfg.Env == setting.Dev {
defaultLogLevel = 10
port = 6443
ip = net.ParseIP("127.0.0.1")
apiURL = fmt.Sprintf("https://%s:%d", ip, port)
}
host := fmt.Sprintf("%s:%d", ip, port)
o.RecommendedOptions.Etcd.StorageConfig.Transport.ServerList = cfg.SectionWithEnvOverrides("grafana-apiserver").Key("etcd_servers").Strings(",")
o.RecommendedOptions.SecureServing.BindAddress = ip
o.RecommendedOptions.SecureServing.BindPort = port
o.RecommendedOptions.Authentication.RemoteKubeConfigFileOptional = true
o.RecommendedOptions.Authorization.RemoteKubeConfigFileOptional = true
o.RecommendedOptions.Admission = nil
o.RecommendedOptions.CoreAPI = nil
o.StorageOptions.StorageType = options.StorageType(cfg.SectionWithEnvOverrides("grafana-apiserver").Key("storage_type").MustString(string(options.StorageTypeLegacy)))
o.StorageOptions.DataPath = filepath.Join(cfg.DataPath, "grafana-apiserver")
o.ExtraOptions.DevMode = features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerEnsureKubectlAccess)
o.ExtraOptions.ExternalAddress = host
o.ExtraOptions.APIURL = apiURL
o.ExtraOptions.Verbosity = defaultLogLevel
}

View File

@ -5,7 +5,7 @@ import (
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/setting"
)

View File

@ -0,0 +1,112 @@
package options
import (
"github.com/spf13/pflag"
v1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
genericfeatures "k8s.io/apiserver/pkg/features"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/server/resourceconfig"
utilfeature "k8s.io/apiserver/pkg/util/feature"
apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1"
aggregatorapiserver "k8s.io/kube-aggregator/pkg/apiserver"
aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme"
aggregatoropenapi "k8s.io/kube-aggregator/pkg/generated/openapi"
"k8s.io/kube-openapi/pkg/common"
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
filestorage "github.com/grafana/grafana/pkg/services/apiserver/storage/file"
)
// AggregatorServerOptions contains the state for the aggregator apiserver
type AggregatorServerOptions struct {
AlternateDNS []string
ProxyClientCertFile string
ProxyClientKeyFile string
}
func NewAggregatorServerOptions() *AggregatorServerOptions {
return &AggregatorServerOptions{}
}
func (o *AggregatorServerOptions) getMergedOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
aggregatorAPIs := aggregatoropenapi.GetOpenAPIDefinitions(ref)
return aggregatorAPIs
}
func (o *AggregatorServerOptions) AddFlags(fs *pflag.FlagSet) {
if o == nil {
return
}
fs.StringVar(&o.ProxyClientCertFile, "proxy-client-cert-file", o.ProxyClientCertFile,
"path to proxy client cert file")
fs.StringVar(&o.ProxyClientKeyFile, "proxy-client-key-file", o.ProxyClientKeyFile,
"path to proxy client cert file")
}
func (o *AggregatorServerOptions) Validate() []error {
if o == nil {
return nil
}
// TODO: do we need to validate anything here?
return nil
}
func (o *AggregatorServerOptions) ApplyTo(aggregatorConfig *aggregatorapiserver.Config, etcdOpts *options.EtcdOptions, dataPath string) error {
genericConfig := aggregatorConfig.GenericConfig
genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
genericConfig.RESTOptionsGetter = nil
if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) &&
utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) {
// Add StorageVersionPrecondition handler to aggregator-apiserver.
// The handler will block write requests to built-in resources until the
// target resources' storage versions are up-to-date.
genericConfig.BuildHandlerChainFunc = genericapiserver.BuildHandlerChainWithStorageVersionPrecondition
}
// copy the etcd options so we don't mutate originals.
// we assume that the etcd options have been completed already. avoid messing with anything outside
// of changes to StorageConfig as that may lead to unexpected behavior when the options are applied.
etcdOptions := *etcdOpts
etcdOptions.StorageConfig.Codec = aggregatorscheme.Codecs.LegacyCodec(v1.SchemeGroupVersion,
apiregistrationv1beta1.SchemeGroupVersion,
servicev0alpha1.SchemeGroupVersion)
etcdOptions.StorageConfig.EncodeVersioner = runtime.NewMultiGroupVersioner(v1.SchemeGroupVersion,
schema.GroupKind{Group: apiregistrationv1beta1.GroupName},
schema.GroupKind{Group: servicev0alpha1.GROUP})
etcdOptions.SkipHealthEndpoints = true // avoid double wiring of health checks
if err := etcdOptions.ApplyTo(&genericConfig.Config); err != nil {
return err
}
// override the RESTOptionsGetter to use the file storage options getter
aggregatorConfig.GenericConfig.RESTOptionsGetter = filestorage.NewRESTOptionsGetter(dataPath, etcdOptions.StorageConfig)
// prevent generic API server from installing the OpenAPI handler. Aggregator server has its own customized OpenAPI handler.
genericConfig.SkipOpenAPIInstallation = true
mergedResourceConfig, err := resourceconfig.MergeAPIResourceConfigs(aggregatorapiserver.DefaultAPIResourceConfigSource(), nil, aggregatorscheme.Scheme)
if err != nil {
return err
}
genericConfig.MergedResourceConfig = mergedResourceConfig
namer := openapinamer.NewDefinitionNamer(aggregatorscheme.Scheme)
genericConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(o.getMergedOpenAPIDefinitions, namer)
genericConfig.OpenAPIV3Config.Info.Title = "Kubernetes"
genericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(o.getMergedOpenAPIDefinitions, namer)
genericConfig.OpenAPIConfig.Info.Title = "Kubernetes"
genericConfig.PostStartHooks = map[string]genericapiserver.PostStartHookConfigEntry{}
// These hooks use v1 informers, which are not available in the grafana aggregator.
genericConfig.DisabledPostStartHooks = genericConfig.DisabledPostStartHooks.Insert("apiservice-status-available-controller")
genericConfig.DisabledPostStartHooks = genericConfig.DisabledPostStartHooks.Insert("start-kube-aggregator-informers")
return nil
}

View File

@ -0,0 +1,46 @@
package options
import (
"strconv"
"github.com/go-logr/logr"
"github.com/spf13/pflag"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
)
type ExtraOptions struct {
DevMode bool
ExternalAddress string
APIURL string
Verbosity int
}
func NewExtraOptions() *ExtraOptions {
return &ExtraOptions{
DevMode: false,
Verbosity: 0,
}
}
func (o *ExtraOptions) AddFlags(fs *pflag.FlagSet) {
fs.BoolVar(&o.DevMode, "grafana-apiserver-dev-mode", o.DevMode, "Enable dev mode")
fs.StringVar(&o.ExternalAddress, "grafana-apiserver-host", o.ExternalAddress, "Host")
fs.StringVar(&o.APIURL, "grafana-apiserver-api-url", o.APIURL, "API URL")
fs.IntVar(&o.Verbosity, "verbosity", o.Verbosity, "Verbosity")
}
func (o *ExtraOptions) Validate() []error {
return nil
}
func (o *ExtraOptions) ApplyTo(c *genericapiserver.RecommendedConfig) error {
logger := logr.New(newLogAdapter(o.Verbosity))
klog.SetLoggerWithOptions(logger, klog.ContextualLogger(true))
if _, err := logs.GlogSetter(strconv.Itoa(o.Verbosity)); err != nil {
logger.Error(err, "failed to set log level")
}
c.ExternalAddress = o.ExternalAddress
return nil
}

View File

@ -1,4 +1,4 @@
package grafanaapiserver
package options
import (
"strings"

View File

@ -0,0 +1,126 @@
package options
import (
"net"
"github.com/spf13/pflag"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/discovery/aggregated"
genericapiserver "k8s.io/apiserver/pkg/server"
genericoptions "k8s.io/apiserver/pkg/server/options"
)
const defaultEtcdPathPrefix = "/registry/grafana.app"
type Options struct {
RecommendedOptions *genericoptions.RecommendedOptions
AggregatorOptions *AggregatorServerOptions
StorageOptions *StorageOptions
ExtraOptions *ExtraOptions
}
func NewOptions(codec runtime.Codec) *Options {
return &Options{
RecommendedOptions: genericoptions.NewRecommendedOptions(
defaultEtcdPathPrefix,
codec,
),
AggregatorOptions: NewAggregatorServerOptions(),
StorageOptions: NewStorageOptions(),
ExtraOptions: NewExtraOptions(),
}
}
func (o *Options) AddFlags(fs *pflag.FlagSet) {
o.RecommendedOptions.AddFlags(fs)
o.AggregatorOptions.AddFlags(fs)
o.StorageOptions.AddFlags(fs)
o.ExtraOptions.AddFlags(fs)
}
func (o *Options) Validate() []error {
if errs := o.ExtraOptions.Validate(); len(errs) != 0 {
return errs
}
if errs := o.StorageOptions.Validate(); len(errs) != 0 {
return errs
}
if errs := o.AggregatorOptions.Validate(); len(errs) != 0 {
return errs
}
if errs := o.RecommendedOptions.SecureServing.Validate(); len(errs) != 0 {
return errs
}
if errs := o.RecommendedOptions.Authentication.Validate(); len(errs) != 0 {
return errs
}
if o.StorageOptions.StorageType == StorageTypeEtcd {
if errs := o.RecommendedOptions.Etcd.Validate(); len(errs) != 0 {
return errs
}
}
return nil
}
func (o *Options) ApplyTo(serverConfig *genericapiserver.RecommendedConfig) error {
serverConfig.AggregatedDiscoveryGroupManager = aggregated.NewResourceManager("apis")
if err := o.ExtraOptions.ApplyTo(serverConfig); err != nil {
return err
}
if !o.ExtraOptions.DevMode {
o.RecommendedOptions.SecureServing.Listener = newFakeListener()
}
if err := o.RecommendedOptions.SecureServing.ApplyTo(&serverConfig.SecureServing, &serverConfig.LoopbackClientConfig); err != nil {
return err
}
if err := o.RecommendedOptions.Authentication.ApplyTo(&serverConfig.Authentication, serverConfig.SecureServing, serverConfig.OpenAPIConfig); err != nil {
return err
}
if !o.ExtraOptions.DevMode {
if err := serverConfig.SecureServing.Listener.Close(); err != nil {
return err
}
serverConfig.SecureServing = nil
}
return nil
}
type fakeListener struct {
server net.Conn
client net.Conn
}
func newFakeListener() *fakeListener {
server, client := net.Pipe()
return &fakeListener{
server: server,
client: client,
}
}
func (f *fakeListener) Accept() (net.Conn, error) {
return f.server, nil
}
func (f *fakeListener) Close() error {
if err := f.client.Close(); err != nil {
return err
}
return f.server.Close()
}
func (f *fakeListener) Addr() net.Addr {
return &net.TCPAddr{IP: net.IPv4(127, 0, 0, 1), Port: 3000, Zone: ""}
}

View File

@ -0,0 +1,51 @@
package options
import (
"fmt"
"github.com/spf13/pflag"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
)
type StorageType string
const (
StorageTypeFile StorageType = "file"
StorageTypeEtcd StorageType = "etcd"
StorageTypeLegacy StorageType = "legacy"
StorageTypeUnified StorageType = "unified"
StorageTypeUnifiedGrpc StorageType = "unified-grpc"
)
type StorageOptions struct {
StorageType StorageType
DataPath string
}
func NewStorageOptions() *StorageOptions {
return &StorageOptions{
StorageType: StorageTypeLegacy,
}
}
func (o *StorageOptions) AddFlags(fs *pflag.FlagSet) {
fs.StringVar((*string)(&o.StorageType), "grafana-apiserver-storage-type", string(o.StorageType), "Storage type")
fs.StringVar((*string)(&o.StorageType), "grafana-apiserver-storage-path", string(o.StorageType), "Storage path for file storage")
}
func (o *StorageOptions) Validate() []error {
errs := []error{}
switch o.StorageType {
case StorageTypeFile, StorageTypeEtcd, StorageTypeLegacy, StorageTypeUnified, StorageTypeUnifiedGrpc:
// no-op
default:
errs = append(errs, fmt.Errorf("--grafana-apiserver-storage-type must be one of %s, %s, %s, %s, %s", StorageTypeFile, StorageTypeEtcd, StorageTypeLegacy, StorageTypeUnified, StorageTypeUnifiedGrpc))
}
return errs
}
func (o *StorageOptions) ApplyTo(serverConfig *genericapiserver.RecommendedConfig, etcdOptions *options.EtcdOptions) error {
// TODO: move storage setup here
return nil
}

View File

@ -1,4 +1,4 @@
package grafanaapiserver
package apiserver
import (
"context"
@ -6,13 +6,8 @@ import (
"net/http"
"net/http/httptest"
"path"
"runtime/debug"
"strconv"
"strings"
"github.com/go-logr/logr"
"github.com/grafana/dskit/services"
"golang.org/x/mod/semver"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -21,15 +16,8 @@ import (
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/endpoints/responsewriter"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
clientrest "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/component-base/logs"
"k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/auth/authorizer"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/appcontext"
@ -38,26 +26,21 @@ import (
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/modules"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/services/apiserver/auth/authorizer"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
grafanaapiserveroptions "github.com/grafana/grafana/pkg/services/apiserver/options"
entitystorage "github.com/grafana/grafana/pkg/services/apiserver/storage/entity"
filestorage "github.com/grafana/grafana/pkg/services/apiserver/storage/file"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
entitystorage "github.com/grafana/grafana/pkg/services/grafana-apiserver/storage/entity"
filestorage "github.com/grafana/grafana/pkg/services/grafana-apiserver/storage/file"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/store/entity"
"github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl"
"github.com/grafana/grafana/pkg/services/store/entity/sqlstash"
"github.com/grafana/grafana/pkg/setting"
)
type StorageType string
const (
StorageTypeFile StorageType = "file"
StorageTypeEtcd StorageType = "etcd"
StorageTypeLegacy StorageType = "legacy"
StorageTypeUnified StorageType = "unified"
StorageTypeUnifiedGrpc StorageType = "unified-grpc"
)
var (
_ Service = (*service)(nil)
_ RestConfigProvider = (*service)(nil)
@ -90,10 +73,6 @@ type Service interface {
registry.CanBeDisabled
}
type APIRegistrar interface {
RegisterAPI(builder APIGroupBuilder)
}
type RestConfigProvider interface {
GetRestConfig() *clientrest.Config
}
@ -111,7 +90,7 @@ type DirectRestConfigProvider interface {
type service struct {
*services.BasicService
config *config
options *grafanaapiserveroptions.Options
restConfig *clientrest.Config
cfg *setting.Cfg
@ -123,7 +102,7 @@ type service struct {
db db.DB
rr routing.RouteRegister
handler http.Handler
builders []APIGroupBuilder
builders []builder.APIGroupBuilder
tracing *tracing.TracingService
@ -139,12 +118,11 @@ func ProvideService(
db db.DB,
) (*service, error) {
s := &service{
config: newConfig(cfg, features),
cfg: cfg,
features: features,
rr: rr,
stopCh: make(chan struct{}),
builders: []APIGroupBuilder{},
builders: []builder.APIGroupBuilder{},
authorizer: authorizer.NewGrafanaAuthorizer(cfg, orgService),
tracing: tracing,
db: db, // For Unified storage
@ -182,7 +160,9 @@ func ProvideService(
}
s.rr.Group("/apis", proxyHandler)
s.rr.Group("/apiserver-metrics", proxyHandler)
s.rr.Group("/livez", proxyHandler)
s.rr.Group("/readyz", proxyHandler)
s.rr.Group("/healthz", proxyHandler)
s.rr.Group("/openapi", proxyHandler)
return s, nil
@ -193,7 +173,7 @@ func (s *service) GetRestConfig() *clientrest.Config {
}
func (s *service) IsDisabled() bool {
return !s.config.enabled
return false
}
// Run is an adapter for the BackgroundService interface.
@ -204,17 +184,11 @@ func (s *service) Run(ctx context.Context) error {
return s.running(ctx)
}
func (s *service) RegisterAPI(builder APIGroupBuilder) {
s.builders = append(s.builders, builder)
func (s *service) RegisterAPI(b builder.APIGroupBuilder) {
s.builders = append(s.builders, b)
}
func (s *service) start(ctx context.Context) error {
logger := logr.New(newLogAdapter(s.config.logLevel))
klog.SetLoggerWithOptions(logger, klog.ContextualLogger(true))
if _, err := logs.GlogSetter(strconv.Itoa(s.config.logLevel)); err != nil {
logger.Error(err, "failed to set log level")
}
// Get the list of groups the server will support
builders := s.builders
@ -226,59 +200,42 @@ func (s *service) start(ctx context.Context) error {
return err
}
// Optionally register a custom authorizer
auth := b.GetAuthorizer()
if auth != nil {
s.authorizer.Register(b.GetGroupVersion(), auth)
}
}
o := options.NewRecommendedOptions("/registry/grafana.app", Codecs.LegacyCodec(groupVersions...))
o.SecureServing.BindAddress = s.config.ip
o.SecureServing.BindPort = s.config.port
o.Authentication.RemoteKubeConfigFileOptional = true
o.Authorization.RemoteKubeConfigFileOptional = true
o := grafanaapiserveroptions.NewOptions(Codecs.LegacyCodec(groupVersions...))
applyGrafanaConfig(s.cfg, s.features, o)
o.Admission = nil
o.CoreAPI = nil
serverConfig := genericapiserver.NewRecommendedConfig(Codecs)
serverConfig.ExternalAddress = s.config.host
if s.config.devMode {
// SecureServingOptions is used when the apiserver needs it's own listener.
// this is not needed in production, but it's useful for development kubectl access.
if err := o.SecureServing.ApplyTo(&serverConfig.SecureServing, &serverConfig.LoopbackClientConfig); err != nil {
return err
}
// AuthenticationOptions is needed to authenticate requests from kubectl in dev mode.
if err := o.Authentication.ApplyTo(&serverConfig.Authentication, serverConfig.SecureServing, serverConfig.OpenAPIConfig); err != nil {
return err
}
} else {
// In production mode, override ExternalAddress and LoopbackClientConfig.
// In dev mode we want to use the loopback client config
// and address provided by SecureServingOptions.
serverConfig.ExternalAddress = s.config.host
serverConfig.LoopbackClientConfig = &clientrest.Config{
Host: s.config.apiURL,
TLSClientConfig: clientrest.TLSClientConfig{
Insecure: true,
},
}
if errs := o.Validate(); len(errs) != 0 {
// TODO: handle multiple errors
return errs[0]
}
switch s.config.storageType {
case StorageTypeEtcd:
o.Etcd.StorageConfig.Transport.ServerList = s.config.etcdServers
if err := o.Etcd.Validate(); len(err) > 0 {
serverConfig := genericapiserver.NewRecommendedConfig(Codecs)
if err := o.ApplyTo(serverConfig); err != nil {
return err
}
serverConfig.Authorization.Authorizer = s.authorizer
serverConfig.TracerProvider = s.tracing.GetTracerProvider()
// setup loopback transport
transport := &roundTripperFunc{ready: make(chan struct{})}
serverConfig.LoopbackClientConfig.Transport = transport
serverConfig.LoopbackClientConfig.TLSClientConfig = clientrest.TLSClientConfig{}
switch o.StorageOptions.StorageType {
case grafanaapiserveroptions.StorageTypeEtcd:
if err := o.RecommendedOptions.Etcd.Validate(); len(err) > 0 {
return err[0]
}
if err := o.Etcd.ApplyTo(&serverConfig.Config); err != nil {
if err := o.RecommendedOptions.Etcd.ApplyTo(&serverConfig.Config); err != nil {
return err
}
case StorageTypeUnified:
case grafanaapiserveroptions.StorageTypeUnified:
if !s.features.IsEnabledGlobally(featuremgmt.FlagUnifiedStorage) {
return fmt.Errorf("unified storage requires the unifiedStorage feature flag (and app_mode = development)")
}
@ -293,9 +250,9 @@ func (s *service) start(ctx context.Context) error {
return err
}
serverConfig.Config.RESTOptionsGetter = entitystorage.NewRESTOptionsGetter(s.cfg, store, o.Etcd.StorageConfig.Codec)
serverConfig.Config.RESTOptionsGetter = entitystorage.NewRESTOptionsGetter(s.cfg, store, o.RecommendedOptions.Etcd.StorageConfig.Codec)
case StorageTypeUnifiedGrpc:
case grafanaapiserveroptions.StorageTypeUnifiedGrpc:
// Create a connection to the gRPC server
// TODO: support configuring the gRPC server address
conn, err := grpc.Dial("localhost:10000", grpc.WithTransportCredentials(insecure.NewCredentials()))
@ -309,20 +266,16 @@ func (s *service) start(ctx context.Context) error {
// Create a client instance
store := entity.NewEntityStoreClientWrapper(conn)
serverConfig.Config.RESTOptionsGetter = entitystorage.NewRESTOptionsGetter(s.cfg, store, o.Etcd.StorageConfig.Codec)
serverConfig.Config.RESTOptionsGetter = entitystorage.NewRESTOptionsGetter(s.cfg, store, o.RecommendedOptions.Etcd.StorageConfig.Codec)
case StorageTypeFile:
serverConfig.RESTOptionsGetter = filestorage.NewRESTOptionsGetter(s.config.dataPath, o.Etcd.StorageConfig)
case StorageTypeLegacy:
// do nothing?
case grafanaapiserveroptions.StorageTypeLegacy:
fallthrough
case grafanaapiserveroptions.StorageTypeFile:
serverConfig.RESTOptionsGetter = filestorage.NewRESTOptionsGetter(o.StorageOptions.DataPath, o.RecommendedOptions.Etcd.StorageConfig)
}
serverConfig.Authorization.Authorizer = s.authorizer
serverConfig.TracerProvider = s.tracing.GetTracerProvider()
// Add OpenAPI specs for each group+version
err := SetupConfig(serverConfig, builders)
err := builder.SetupConfig(Scheme, serverConfig, builders)
if err != nil {
return err
}
@ -339,28 +292,37 @@ func (s *service) start(ctx context.Context) error {
return err
}
dualWriteEnabled := o.StorageOptions.StorageType != grafanaapiserveroptions.StorageTypeLegacy
// Install the API Group+version
err = InstallAPIs(server, serverConfig.RESTOptionsGetter, builders)
err = builder.InstallAPIs(Scheme, Codecs, server, serverConfig.RESTOptionsGetter, builders, dualWriteEnabled)
if err != nil {
return err
}
// set the transport function and signal that it's ready
transport.fn = func(req *http.Request) (*http.Response, error) {
w := newWrappedResponseWriter()
resp := responsewriter.WrapForHTTP1Or2(w)
server.Handler.ServeHTTP(resp, req)
return w.Result(), nil
}
close(transport.ready)
// only write kubeconfig in dev mode
if o.ExtraOptions.DevMode {
if err := ensureKubeConfig(server.LoopbackClientConfig, o.StorageOptions.DataPath); err != nil {
return err
}
}
// Used by the proxy wrapper registered in ProvideService
s.handler = server.Handler
s.restConfig = server.LoopbackClientConfig
s.options = o
prepared := server.PrepareRun()
// When running in production, do not start a standalone https server
if !s.config.devMode {
return nil
}
// only write kubeconfig in dev mode
if err := s.ensureKubeConfig(); err != nil {
return err
}
go func() {
s.stoppedCh <- prepared.Run(s.stopCh)
}()
@ -385,12 +347,6 @@ func (s *service) DirectlyServeHTTP(w http.ResponseWriter, r *http.Request) {
}
func (s *service) running(ctx context.Context) error {
// skip waiting for the server in prod mode
if !s.config.devMode {
<-ctx.Done()
return nil
}
select {
case err := <-s.stoppedCh:
if err != nil {
@ -402,47 +358,42 @@ func (s *service) running(ctx context.Context) error {
return nil
}
func (s *service) ensureKubeConfig() error {
func ensureKubeConfig(restConfig *clientrest.Config, dir string) error {
return clientcmd.WriteToFile(
utils.FormatKubeConfig(s.restConfig),
path.Join(s.config.dataPath, "grafana.kubeconfig"),
utils.FormatKubeConfig(restConfig),
path.Join(dir, "grafana.kubeconfig"),
)
}
type roundTripperFunc struct {
fn func(req *http.Request) (*http.Response, error)
ready chan struct{}
fn func(req *http.Request) (*http.Response, error)
}
func (f *roundTripperFunc) RoundTrip(req *http.Request) (*http.Response, error) {
if f.fn == nil {
<-f.ready
}
return f.fn(req)
}
// find the k8s version according to build info
func getK8sApiserverVersion() (string, error) {
bi, ok := debug.ReadBuildInfo()
if !ok {
return "", fmt.Errorf("debug.ReadBuildInfo() failed")
}
var _ http.ResponseWriter = (*wrappedResponseWriter)(nil)
var _ responsewriter.UserProvidedDecorator = (*wrappedResponseWriter)(nil)
if len(bi.Deps) == 0 {
return "v?.?", nil // this is normal while debugging
}
for _, dep := range bi.Deps {
if dep.Path == "k8s.io/apiserver" {
if !semver.IsValid(dep.Version) {
return "", fmt.Errorf("invalid semantic version for k8s.io/apiserver")
}
// v0 => v1
majorVersion := strings.TrimPrefix(semver.Major(dep.Version), "v")
majorInt, err := strconv.Atoi(majorVersion)
if err != nil {
return "", fmt.Errorf("could not convert majorVersion to int. majorVersion: %s", majorVersion)
}
newMajor := fmt.Sprintf("v%d", majorInt+1)
return strings.Replace(dep.Version, semver.Major(dep.Version), newMajor, 1), nil
}
}
return "", fmt.Errorf("could not find k8s.io/apiserver in build info")
type wrappedResponseWriter struct {
*httptest.ResponseRecorder
}
func newWrappedResponseWriter() *wrappedResponseWriter {
w := httptest.NewRecorder()
return &wrappedResponseWriter{w}
}
func (w *wrappedResponseWriter) Unwrap() http.ResponseWriter {
return w.ResponseRecorder
}
func (w *wrappedResponseWriter) CloseNotify() <-chan bool {
// TODO: this is probably not the right thing to do here
return make(<-chan bool)
}

View File

@ -15,7 +15,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/endpoints/request"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
entityStore "github.com/grafana/grafana/pkg/services/store/entity"
)

View File

@ -8,7 +8,7 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
)
type TestResource struct {

View File

@ -11,7 +11,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/utils"
"github.com/grafana/grafana/pkg/services/apiserver/utils"
)
func TestTableConverter(t *testing.T) {

View File

@ -1,13 +1,15 @@
package grafanaapiserver
package apiserver
import (
"github.com/google/wire"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
)
var WireSet = wire.NewSet(
ProvideService,
wire.Bind(new(RestConfigProvider), new(*service)),
wire.Bind(new(Service), new(*service)),
wire.Bind(new(APIRegistrar), new(*service)),
wire.Bind(new(DirectRestConfigProvider), new(*service)),
wire.Bind(new(builder.APIRegistrar), new(*service)),
)

View File

@ -1,60 +0,0 @@
package grafanaapiserver
import (
"fmt"
"net"
"path/filepath"
"strconv"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
type config struct {
enabled bool
devMode bool
ip net.IP
port int
host string
apiURL string
storageType StorageType
etcdServers []string
dataPath string
logLevel int
}
func newConfig(cfg *setting.Cfg, features featuremgmt.FeatureToggles) *config {
defaultLogLevel := 0
ip := net.ParseIP(cfg.HTTPAddr)
apiURL := cfg.AppURL
port, err := strconv.Atoi(cfg.HTTPPort)
if err != nil {
port = 3000
}
if cfg.Env == setting.Dev {
defaultLogLevel = 10
port = 6443
ip = net.ParseIP("127.0.0.1")
apiURL = fmt.Sprintf("https://%s:%d", ip, port)
}
host := fmt.Sprintf("%s:%d", ip, port)
return &config{
enabled: true,
devMode: features.IsEnabledGlobally(featuremgmt.FlagGrafanaAPIServerEnsureKubectlAccess),
dataPath: filepath.Join(cfg.DataPath, "grafana-apiserver"),
ip: ip,
port: port,
host: host,
logLevel: cfg.SectionWithEnvOverrides("grafana-apiserver").Key("log_level").MustInt(defaultLogLevel),
etcdServers: cfg.SectionWithEnvOverrides("grafana-apiserver").Key("etcd_servers").Strings(","),
storageType: StorageType(cfg.SectionWithEnvOverrides("grafana-apiserver").Key("storage_type").MustString(string(StorageTypeLegacy))),
apiURL: apiURL,
}
}

View File

@ -1,40 +0,0 @@
package grafanaapiserver
import (
"net"
"testing"
"github.com/stretchr/testify/require"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
)
func TestNewConfig(t *testing.T) {
cfg := setting.NewCfg()
cfg.Env = setting.Prod
cfg.DataPath = "/tmp/grafana"
cfg.HTTPAddr = "10.0.0.1"
cfg.HTTPPort = "4000"
cfg.AppURL = "http://test:4000"
section := cfg.Raw.Section("grafana-apiserver")
section.Key("log_level").SetValue("5")
section.Key("etcd_servers").SetValue("http://localhost:2379")
actual := newConfig(cfg, featuremgmt.WithFeatures())
expected := &config{
enabled: true,
devMode: false,
storageType: StorageTypeLegacy,
etcdServers: []string{"http://localhost:2379"},
apiURL: "http://test:4000",
ip: net.ParseIP("10.0.0.1"),
port: 4000,
host: "10.0.0.1:4000",
dataPath: "/tmp/grafana/grafana-apiserver",
logLevel: 5,
}
require.Equal(t, expected, actual)
}

View File

@ -25,9 +25,9 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/server"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/auth/identity"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/org/orgimpl"
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"