mirror of https://github.com/k3s-io/k3s
269 lines
11 KiB
Go
269 lines
11 KiB
Go
/*
|
|
Copyright 2017 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package apiserver
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"time"
|
|
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/install"
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
_ "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
|
"k8s.io/apiextensions-apiserver/pkg/client/clientset/internalclientset"
|
|
_ "k8s.io/apiextensions-apiserver/pkg/client/informers/externalversions"
|
|
internalinformers "k8s.io/apiextensions-apiserver/pkg/client/informers/internalversion"
|
|
"k8s.io/apiextensions-apiserver/pkg/controller/apiapproval"
|
|
"k8s.io/apiextensions-apiserver/pkg/controller/establish"
|
|
"k8s.io/apiextensions-apiserver/pkg/controller/finalizer"
|
|
"k8s.io/apiextensions-apiserver/pkg/controller/nonstructuralschema"
|
|
openapicontroller "k8s.io/apiextensions-apiserver/pkg/controller/openapi"
|
|
"k8s.io/apiextensions-apiserver/pkg/controller/status"
|
|
apiextensionsfeatures "k8s.io/apiextensions-apiserver/pkg/features"
|
|
"k8s.io/apiextensions-apiserver/pkg/registry/customresourcedefinition"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer"
|
|
"k8s.io/apimachinery/pkg/util/wait"
|
|
"k8s.io/apimachinery/pkg/version"
|
|
"k8s.io/apiserver/pkg/endpoints/discovery"
|
|
genericregistry "k8s.io/apiserver/pkg/registry/generic"
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
|
serverstorage "k8s.io/apiserver/pkg/server/storage"
|
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
|
"k8s.io/apiserver/pkg/util/webhook"
|
|
)
|
|
|
|
var (
|
|
Scheme = runtime.NewScheme()
|
|
Codecs = serializer.NewCodecFactory(Scheme)
|
|
|
|
// if you modify this, make sure you update the crEncoder
|
|
unversionedVersion = schema.GroupVersion{Group: "", Version: "v1"}
|
|
unversionedTypes = []runtime.Object{
|
|
&metav1.Status{},
|
|
&metav1.WatchEvent{},
|
|
&metav1.APIVersions{},
|
|
&metav1.APIGroupList{},
|
|
&metav1.APIGroup{},
|
|
&metav1.APIResourceList{},
|
|
}
|
|
)
|
|
|
|
func init() {
|
|
install.Install(Scheme)
|
|
|
|
// we need to add the options to empty v1
|
|
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})
|
|
|
|
Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...)
|
|
}
|
|
|
|
type ExtraConfig struct {
|
|
CRDRESTOptionsGetter genericregistry.RESTOptionsGetter
|
|
|
|
// MasterCount is used to detect whether cluster is HA, and if it is
|
|
// the CRD Establishing will be hold by 5 seconds.
|
|
MasterCount int
|
|
|
|
// ServiceResolver is used in CR webhook converters to resolve webhook's service names
|
|
ServiceResolver webhook.ServiceResolver
|
|
// AuthResolverWrapper is used in CR webhook converters
|
|
AuthResolverWrapper webhook.AuthenticationInfoResolverWrapper
|
|
}
|
|
|
|
type Config struct {
|
|
GenericConfig *genericapiserver.RecommendedConfig
|
|
ExtraConfig ExtraConfig
|
|
}
|
|
|
|
type completedConfig struct {
|
|
GenericConfig genericapiserver.CompletedConfig
|
|
ExtraConfig *ExtraConfig
|
|
}
|
|
|
|
type CompletedConfig struct {
|
|
// Embed a private pointer that cannot be instantiated outside of this package.
|
|
*completedConfig
|
|
}
|
|
|
|
type CustomResourceDefinitions struct {
|
|
GenericAPIServer *genericapiserver.GenericAPIServer
|
|
|
|
// provided for easier embedding
|
|
Informers internalinformers.SharedInformerFactory
|
|
}
|
|
|
|
// Complete fills in any fields not set that are required to have valid data. It's mutating the receiver.
|
|
func (cfg *Config) Complete() CompletedConfig {
|
|
c := completedConfig{
|
|
cfg.GenericConfig.Complete(),
|
|
&cfg.ExtraConfig,
|
|
}
|
|
|
|
c.GenericConfig.EnableDiscovery = false
|
|
c.GenericConfig.Version = &version.Info{
|
|
Major: "0",
|
|
Minor: "1",
|
|
}
|
|
|
|
return CompletedConfig{&c}
|
|
}
|
|
|
|
// New returns a new instance of CustomResourceDefinitions from the given config.
|
|
func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) (*CustomResourceDefinitions, error) {
|
|
genericServer, err := c.GenericConfig.New("apiextensions-apiserver", delegationTarget)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
s := &CustomResourceDefinitions{
|
|
GenericAPIServer: genericServer,
|
|
}
|
|
|
|
apiResourceConfig := c.GenericConfig.MergedResourceConfig
|
|
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiextensions.GroupName, Scheme, metav1.ParameterCodec, Codecs)
|
|
if apiResourceConfig.VersionEnabled(v1beta1.SchemeGroupVersion) {
|
|
storage := map[string]rest.Storage{}
|
|
// customresourcedefinitions
|
|
customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
|
|
storage["customresourcedefinitions"] = customResourceDefintionStorage
|
|
storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage)
|
|
|
|
apiGroupInfo.VersionedResourcesStorageMap[v1beta1.SchemeGroupVersion.Version] = storage
|
|
}
|
|
if apiResourceConfig.VersionEnabled(v1.SchemeGroupVersion) {
|
|
storage := map[string]rest.Storage{}
|
|
// customresourcedefinitions
|
|
customResourceDefintionStorage := customresourcedefinition.NewREST(Scheme, c.GenericConfig.RESTOptionsGetter)
|
|
storage["customresourcedefinitions"] = customResourceDefintionStorage
|
|
storage["customresourcedefinitions/status"] = customresourcedefinition.NewStatusREST(Scheme, customResourceDefintionStorage)
|
|
|
|
apiGroupInfo.VersionedResourcesStorageMap[v1.SchemeGroupVersion.Version] = storage
|
|
}
|
|
|
|
if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
crdClient, err := internalclientset.NewForConfig(s.GenericAPIServer.LoopbackClientConfig)
|
|
if err != nil {
|
|
// it's really bad that this is leaking here, but until we can fix the test (which I'm pretty sure isn't even testing what it wants to test),
|
|
// we need to be able to move forward
|
|
return nil, fmt.Errorf("failed to create clientset: %v", err)
|
|
}
|
|
s.Informers = internalinformers.NewSharedInformerFactory(crdClient, 5*time.Minute)
|
|
|
|
delegateHandler := delegationTarget.UnprotectedHandler()
|
|
if delegateHandler == nil {
|
|
delegateHandler = http.NotFoundHandler()
|
|
}
|
|
|
|
versionDiscoveryHandler := &versionDiscoveryHandler{
|
|
discovery: map[schema.GroupVersion]*discovery.APIVersionHandler{},
|
|
delegate: delegateHandler,
|
|
}
|
|
groupDiscoveryHandler := &groupDiscoveryHandler{
|
|
discovery: map[string]*discovery.APIGroupHandler{},
|
|
delegate: delegateHandler,
|
|
}
|
|
establishingController := establish.NewEstablishingController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
|
|
crdHandler, err := NewCustomResourceDefinitionHandler(
|
|
versionDiscoveryHandler,
|
|
groupDiscoveryHandler,
|
|
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
|
|
delegateHandler,
|
|
c.ExtraConfig.CRDRESTOptionsGetter,
|
|
c.GenericConfig.AdmissionControl,
|
|
establishingController,
|
|
c.ExtraConfig.ServiceResolver,
|
|
c.ExtraConfig.AuthResolverWrapper,
|
|
c.ExtraConfig.MasterCount,
|
|
s.GenericAPIServer.Authorizer,
|
|
c.GenericConfig.RequestTimeout,
|
|
time.Duration(c.GenericConfig.MinRequestTimeout)*time.Second,
|
|
apiGroupInfo.StaticOpenAPISpec,
|
|
c.GenericConfig.MaxRequestBodyBytes,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", crdHandler)
|
|
s.GenericAPIServer.Handler.NonGoRestfulMux.HandlePrefix("/apis/", crdHandler)
|
|
|
|
crdController := NewDiscoveryController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), versionDiscoveryHandler, groupDiscoveryHandler)
|
|
namingController := status.NewNamingConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
|
|
nonStructuralSchemaController := nonstructuralschema.NewConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
|
|
apiApprovalController := apiapproval.NewKubernetesAPIApprovalPolicyConformantConditionController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(), crdClient.Apiextensions())
|
|
finalizingController := finalizer.NewCRDFinalizer(
|
|
s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions(),
|
|
crdClient.Apiextensions(),
|
|
crdHandler,
|
|
)
|
|
var openapiController *openapicontroller.Controller
|
|
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourcePublishOpenAPI) {
|
|
openapiController = openapicontroller.NewController(s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions())
|
|
}
|
|
|
|
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-informers", func(context genericapiserver.PostStartHookContext) error {
|
|
s.Informers.Start(context.StopCh)
|
|
return nil
|
|
})
|
|
s.GenericAPIServer.AddPostStartHookOrDie("start-apiextensions-controllers", func(context genericapiserver.PostStartHookContext) error {
|
|
// OpenAPIVersionedService and StaticOpenAPISpec are populated in generic apiserver PrepareRun().
|
|
// Together they serve the /openapi/v2 endpoint on a generic apiserver. A generic apiserver may
|
|
// choose to not enable OpenAPI by having null openAPIConfig, and thus OpenAPIVersionedService
|
|
// and StaticOpenAPISpec are both null. In that case we don't run the CRD OpenAPI controller.
|
|
if utilfeature.DefaultFeatureGate.Enabled(apiextensionsfeatures.CustomResourcePublishOpenAPI) && s.GenericAPIServer.OpenAPIVersionedService != nil && s.GenericAPIServer.StaticOpenAPISpec != nil {
|
|
go openapiController.Run(s.GenericAPIServer.StaticOpenAPISpec, s.GenericAPIServer.OpenAPIVersionedService, context.StopCh)
|
|
}
|
|
|
|
go crdController.Run(context.StopCh)
|
|
go namingController.Run(context.StopCh)
|
|
go establishingController.Run(context.StopCh)
|
|
go nonStructuralSchemaController.Run(5, context.StopCh)
|
|
go apiApprovalController.Run(5, context.StopCh)
|
|
go finalizingController.Run(5, context.StopCh)
|
|
return nil
|
|
})
|
|
// we don't want to report healthy until we can handle all CRDs that have already been registered. Waiting for the informer
|
|
// to sync makes sure that the lister will be valid before we begin. There may still be races for CRDs added after startup,
|
|
// but we won't go healthy until we can handle the ones already present.
|
|
s.GenericAPIServer.AddPostStartHookOrDie("crd-informer-synced", func(context genericapiserver.PostStartHookContext) error {
|
|
return wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) {
|
|
return s.Informers.Apiextensions().InternalVersion().CustomResourceDefinitions().Informer().HasSynced(), nil
|
|
}, context.StopCh)
|
|
})
|
|
|
|
return s, nil
|
|
}
|
|
|
|
func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig {
|
|
ret := serverstorage.NewResourceConfig()
|
|
// NOTE: GroupVersions listed here will be enabled by default. Don't put alpha versions in the list.
|
|
ret.EnableVersions(
|
|
v1beta1.SchemeGroupVersion,
|
|
v1.SchemeGroupVersion,
|
|
)
|
|
|
|
return ret
|
|
}
|