diff --git a/vendor/k8s.io/apimachinery/pkg/api/meta/table/table.go b/vendor/k8s.io/apimachinery/pkg/api/meta/table/table.go new file mode 100644 index 000000000000..1887f32626bc --- /dev/null +++ b/vendor/k8s.io/apimachinery/pkg/api/meta/table/table.go @@ -0,0 +1,70 @@ +/* +Copyright 2018 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 table + +import ( + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/duration" +) + +// MetaToTableRow converts a list or object into one or more table rows. The provided rowFn is invoked for +// each accessed item, with name and age being passed to each. +func MetaToTableRow(obj runtime.Object, rowFn func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error)) ([]metav1.TableRow, error) { + if meta.IsListType(obj) { + rows := make([]metav1.TableRow, 0, 16) + err := meta.EachListItem(obj, func(obj runtime.Object) error { + nestedRows, err := MetaToTableRow(obj, rowFn) + if err != nil { + return err + } + rows = append(rows, nestedRows...) + return nil + }) + if err != nil { + return nil, err + } + return rows, nil + } + + rows := make([]metav1.TableRow, 0, 1) + m, err := meta.Accessor(obj) + if err != nil { + return nil, err + } + row := metav1.TableRow{ + Object: runtime.RawExtension{Object: obj}, + } + row.Cells, err = rowFn(obj, m, m.GetName(), ConvertToHumanReadableDateType(m.GetCreationTimestamp())) + if err != nil { + return nil, err + } + rows = append(rows, row) + return rows, nil +} + +// ConvertToHumanReadableDateType returns the elapsed time since timestamp in +// human-readable approximation. +func ConvertToHumanReadableDateType(timestamp metav1.Time) string { + if timestamp.IsZero() { + return "" + } + return duration.HumanDuration(time.Since(timestamp.Time)) +} diff --git a/vendor/k8s.io/apiserver/pkg/util/proxy/proxy.go b/vendor/k8s.io/apiserver/pkg/util/proxy/proxy.go new file mode 100644 index 000000000000..f0a4901b3700 --- /dev/null +++ b/vendor/k8s.io/apiserver/pkg/util/proxy/proxy.go @@ -0,0 +1,119 @@ +/* +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 proxy + +import ( + "fmt" + "math/rand" + "net" + "net/url" + "strconv" + + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + listersv1 "k8s.io/client-go/listers/core/v1" +) + +// findServicePort finds the service port by name or numerically. +func findServicePort(svc *v1.Service, port int32) (*v1.ServicePort, error) { + for _, svcPort := range svc.Spec.Ports { + if svcPort.Port == port { + return &svcPort, nil + } + } + return nil, errors.NewServiceUnavailable(fmt.Sprintf("no service port %d found for service %q", port, svc.Name)) +} + +// ResourceLocation returns a URL to which one can send traffic for the specified service. +func ResolveEndpoint(services listersv1.ServiceLister, endpoints listersv1.EndpointsLister, namespace, id string, port int32) (*url.URL, error) { + svc, err := services.Services(namespace).Get(id) + if err != nil { + return nil, err + } + + switch { + case svc.Spec.Type == v1.ServiceTypeClusterIP, svc.Spec.Type == v1.ServiceTypeLoadBalancer, svc.Spec.Type == v1.ServiceTypeNodePort: + // these are fine + default: + return nil, fmt.Errorf("unsupported service type %q", svc.Spec.Type) + } + + svcPort, err := findServicePort(svc, port) + if err != nil { + return nil, err + } + + eps, err := endpoints.Endpoints(namespace).Get(svc.Name) + if err != nil { + return nil, err + } + if len(eps.Subsets) == 0 { + return nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", svc.Name)) + } + + // Pick a random Subset to start searching from. + ssSeed := rand.Intn(len(eps.Subsets)) + + // Find a Subset that has the port. + for ssi := 0; ssi < len(eps.Subsets); ssi++ { + ss := &eps.Subsets[(ssSeed+ssi)%len(eps.Subsets)] + if len(ss.Addresses) == 0 { + continue + } + for i := range ss.Ports { + if ss.Ports[i].Name == svcPort.Name { + // Pick a random address. + ip := ss.Addresses[rand.Intn(len(ss.Addresses))].IP + port := int(ss.Ports[i].Port) + return &url.URL{ + Scheme: "https", + Host: net.JoinHostPort(ip, strconv.Itoa(port)), + }, nil + } + } + } + return nil, errors.NewServiceUnavailable(fmt.Sprintf("no endpoints available for service %q", id)) +} + +func ResolveCluster(services listersv1.ServiceLister, namespace, id string, port int32) (*url.URL, error) { + svc, err := services.Services(namespace).Get(id) + if err != nil { + return nil, err + } + + switch { + case svc.Spec.Type == v1.ServiceTypeClusterIP && svc.Spec.ClusterIP == v1.ClusterIPNone: + return nil, fmt.Errorf(`cannot route to service with ClusterIP "None"`) + // use IP from a clusterIP for these service types + case svc.Spec.Type == v1.ServiceTypeClusterIP, svc.Spec.Type == v1.ServiceTypeLoadBalancer, svc.Spec.Type == v1.ServiceTypeNodePort: + svcPort, err := findServicePort(svc, port) + if err != nil { + return nil, err + } + return &url.URL{ + Scheme: "https", + Host: net.JoinHostPort(svc.Spec.ClusterIP, fmt.Sprintf("%d", svcPort.Port)), + }, nil + case svc.Spec.Type == v1.ServiceTypeExternalName: + return &url.URL{ + Scheme: "https", + Host: net.JoinHostPort(svc.Spec.ExternalName, fmt.Sprintf("%d", port)), + }, nil + default: + return nil, fmt.Errorf("unsupported service type %q", svc.Spec.Type) + } +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/install/install.go b/vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/install/install.go new file mode 100644 index 000000000000..75dd8b5d492d --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/install/install.go @@ -0,0 +1,33 @@ +/* +Copyright 2016 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 install + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/kube-aggregator/pkg/apis/apiregistration" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" +) + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(apiregistration.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) + utilruntime.Must(v1beta1.AddToScheme(scheme)) + utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion)) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/validation/validation.go b/vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/validation/validation.go new file mode 100644 index 000000000000..82754d622a00 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apis/apiregistration/validation/validation.go @@ -0,0 +1,125 @@ +/* +Copyright 2016 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 validation + +import ( + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/api/validation/path" + utilvalidation "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + + "k8s.io/kube-aggregator/pkg/apis/apiregistration" +) + +// ValidateAPIService validates that the APIService is correctly defined. +func ValidateAPIService(apiService *apiregistration.APIService) field.ErrorList { + requiredName := apiService.Spec.Version + "." + apiService.Spec.Group + + allErrs := validation.ValidateObjectMeta(&apiService.ObjectMeta, false, + func(name string, prefix bool) []string { + if minimalFailures := path.IsValidPathSegmentName(name); len(minimalFailures) > 0 { + return minimalFailures + } + // the name *must* be version.group + if name != requiredName { + return []string{fmt.Sprintf("must be `spec.version+\".\"+spec.group`: %q", requiredName)} + } + + return []string{} + }, + field.NewPath("metadata")) + + // in this case we allow empty group + if len(apiService.Spec.Group) == 0 && apiService.Spec.Version != "v1" { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "group"), "only v1 may have an empty group and it better be legacy kube")) + } + if len(apiService.Spec.Group) > 0 { + for _, errString := range utilvalidation.IsDNS1123Subdomain(apiService.Spec.Group) { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "group"), apiService.Spec.Group, errString)) + } + } + + for _, errString := range utilvalidation.IsDNS1035Label(apiService.Spec.Version) { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "version"), apiService.Spec.Version, errString)) + } + + if apiService.Spec.GroupPriorityMinimum <= 0 || apiService.Spec.GroupPriorityMinimum > 20000 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "groupPriorityMinimum"), apiService.Spec.GroupPriorityMinimum, "must be positive and less than 20000")) + } + if apiService.Spec.VersionPriority <= 0 || apiService.Spec.VersionPriority > 1000 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "versionPriority"), apiService.Spec.VersionPriority, "must be positive and less than 1000")) + } + + if apiService.Spec.Service == nil { + if len(apiService.Spec.CABundle) != 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "caBundle"), fmt.Sprintf("%d bytes", len(apiService.Spec.CABundle)), "local APIServices may not have a caBundle")) + } + if apiService.Spec.InsecureSkipTLSVerify { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "insecureSkipTLSVerify"), apiService.Spec.InsecureSkipTLSVerify, "local APIServices may not have insecureSkipTLSVerify")) + } + return allErrs + } + + if len(apiService.Spec.Service.Namespace) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "service", "namespace"), "")) + } + if len(apiService.Spec.Service.Name) == 0 { + allErrs = append(allErrs, field.Required(field.NewPath("spec", "service", "name"), "")) + } + if errs := utilvalidation.IsValidPortNum(int(apiService.Spec.Service.Port)); errs != nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "service", "port"), apiService.Spec.Service.Port, "port is not valid: "+strings.Join(errs, ", "))) + } + if apiService.Spec.InsecureSkipTLSVerify && len(apiService.Spec.CABundle) > 0 { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec", "insecureSkipTLSVerify"), apiService.Spec.InsecureSkipTLSVerify, "may not be true if caBundle is present")) + } + + return allErrs +} + +// ValidateAPIServiceUpdate validates an update of APIService. +func ValidateAPIServiceUpdate(newAPIService *apiregistration.APIService, oldAPIService *apiregistration.APIService) field.ErrorList { + allErrs := validation.ValidateObjectMetaUpdate(&newAPIService.ObjectMeta, &oldAPIService.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateAPIService(newAPIService)...) + + return allErrs +} + +// ValidateAPIServiceStatus validates that the APIService status is one of 'True', 'False' or 'Unknown'. +func ValidateAPIServiceStatus(status *apiregistration.APIServiceStatus, fldPath *field.Path) field.ErrorList { + allErrs := field.ErrorList{} + + for i, condition := range status.Conditions { + if condition.Status != apiregistration.ConditionTrue && + condition.Status != apiregistration.ConditionFalse && + condition.Status != apiregistration.ConditionUnknown { + allErrs = append(allErrs, field.NotSupported(fldPath.Child("conditions").Index(i).Child("status"), condition.Status, []string{ + string(apiregistration.ConditionTrue), string(apiregistration.ConditionFalse), string(apiregistration.ConditionUnknown)})) + } + } + + return allErrs +} + +// ValidateAPIServiceStatusUpdate validates an update of the status field of APIService. +func ValidateAPIServiceStatusUpdate(newAPIService *apiregistration.APIService, oldAPIService *apiregistration.APIService) field.ErrorList { + allErrs := validation.ValidateObjectMetaUpdate(&newAPIService.ObjectMeta, &oldAPIService.ObjectMeta, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateAPIServiceStatus(&newAPIService.Status, field.NewPath("status"))...) + return allErrs +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go new file mode 100644 index 000000000000..d60f8df9f669 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/apiserver.go @@ -0,0 +1,523 @@ +/* +Copyright 2016 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 ( + "context" + "fmt" + "net/http" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/wait" + genericfeatures "k8s.io/apiserver/pkg/features" + genericapiserver "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/server/egressselector" + serverstorage "k8s.io/apiserver/pkg/server/storage" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/pkg/version" + openapicommon "k8s.io/kube-openapi/pkg/common" + + "k8s.io/apiserver/pkg/server/dynamiccertificates" + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + v1helper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" + aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" + "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" + informers "k8s.io/kube-aggregator/pkg/client/informers/externalversions" + listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1" + openapicontroller "k8s.io/kube-aggregator/pkg/controllers/openapi" + openapiaggregator "k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator" + openapiv3controller "k8s.io/kube-aggregator/pkg/controllers/openapiv3" + openapiv3aggregator "k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator" + statuscontrollers "k8s.io/kube-aggregator/pkg/controllers/status" + apiservicerest "k8s.io/kube-aggregator/pkg/registry/apiservice/rest" +) + +func init() { + // we need to add the options (like ListOptions) to empty v1 + metav1.AddToGroupVersion(aggregatorscheme.Scheme, schema.GroupVersion{Group: "", Version: "v1"}) + + unversioned := schema.GroupVersion{Group: "", Version: "v1"} + aggregatorscheme.Scheme.AddUnversionedTypes(unversioned, + &metav1.Status{}, + &metav1.APIVersions{}, + &metav1.APIGroupList{}, + &metav1.APIGroup{}, + &metav1.APIResourceList{}, + ) +} + +const ( + // legacyAPIServiceName is the fixed name of the only non-groupified API version + legacyAPIServiceName = "v1." + // StorageVersionPostStartHookName is the name of the storage version updater post start hook. + StorageVersionPostStartHookName = "built-in-resources-storage-version-updater" +) + +// ExtraConfig represents APIServices-specific configuration +type ExtraConfig struct { + // ProxyClientCert/Key are the client cert used to identify this proxy. Backing APIServices use + // this to confirm the proxy's identity + ProxyClientCertFile string + ProxyClientKeyFile string + + // If present, the Dial method will be used for dialing out to delegate + // apiservers. + ProxyTransport *http.Transport + + // Mechanism by which the Aggregator will resolve services. Required. + ServiceResolver ServiceResolver + + RejectForwardingRedirects bool +} + +// Config represents the configuration needed to create an APIAggregator. +type Config struct { + GenericConfig *genericapiserver.RecommendedConfig + ExtraConfig ExtraConfig +} + +type completedConfig struct { + GenericConfig genericapiserver.CompletedConfig + ExtraConfig *ExtraConfig +} + +// CompletedConfig same as Config, just to swap private object. +type CompletedConfig struct { + // Embed a private pointer that cannot be instantiated outside of this package. + *completedConfig +} + +type runnable interface { + Run(stopCh <-chan struct{}) error +} + +// preparedGenericAPIServer is a private wrapper that enforces a call of PrepareRun() before Run can be invoked. +type preparedAPIAggregator struct { + *APIAggregator + runnable runnable +} + +// APIAggregator contains state for a Kubernetes cluster master/api server. +type APIAggregator struct { + GenericAPIServer *genericapiserver.GenericAPIServer + + // provided for easier embedding + APIRegistrationInformers informers.SharedInformerFactory + + delegateHandler http.Handler + + // proxyCurrentCertKeyContent holds he client cert used to identify this proxy. Backing APIServices use this to confirm the proxy's identity + proxyCurrentCertKeyContent certKeyFunc + proxyTransport *http.Transport + + // proxyHandlers are the proxy handlers that are currently registered, keyed by apiservice.name + proxyHandlers map[string]*proxyHandler + // handledGroups are the groups that already have routes + handledGroups sets.String + + // lister is used to add group handling for /apis/ aggregator lookups based on + // controller state + lister listers.APIServiceLister + + // Information needed to determine routing for the aggregator + serviceResolver ServiceResolver + + // Enable swagger and/or OpenAPI if these configs are non-nil. + openAPIConfig *openapicommon.Config + + // Enable OpenAPI V3 if these configs are non-nil + openAPIV3Config *openapicommon.Config + + // openAPIAggregationController downloads and merges OpenAPI v2 specs. + openAPIAggregationController *openapicontroller.AggregationController + + // openAPIV3AggregationController downloads and caches OpenAPI v3 specs. + openAPIV3AggregationController *openapiv3controller.AggregationController + + // egressSelector selects the proper egress dialer to communicate with the custom apiserver + // overwrites proxyTransport dialer if not nil + egressSelector *egressselector.EgressSelector + + // rejectForwardingRedirects is whether to allow to forward redirect response + rejectForwardingRedirects bool +} + +// 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, + } + + // the kube aggregator wires its own discovery mechanism + // TODO eventually collapse this by extracting all of the discovery out + c.GenericConfig.EnableDiscovery = false + version := version.Get() + c.GenericConfig.Version = &version + + return CompletedConfig{&c} +} + +// NewWithDelegate returns a new instance of APIAggregator from the given config. +func (c completedConfig) NewWithDelegate(delegationTarget genericapiserver.DelegationTarget) (*APIAggregator, error) { + genericServer, err := c.GenericConfig.New("kube-aggregator", delegationTarget) + if err != nil { + return nil, err + } + + apiregistrationClient, err := clientset.NewForConfig(c.GenericConfig.LoopbackClientConfig) + if err != nil { + return nil, err + } + informerFactory := informers.NewSharedInformerFactory( + apiregistrationClient, + 5*time.Minute, // this is effectively used as a refresh interval right now. Might want to do something nicer later on. + ) + + // apiServiceRegistrationControllerInitiated is closed when APIServiceRegistrationController has finished "installing" all known APIServices. + // At this point we know that the proxy handler knows about APIServices and can handle client requests. + // Before it might have resulted in a 404 response which could have serious consequences for some controllers like GC and NS + // + // Note that the APIServiceRegistrationController waits for APIServiceInformer to synced before doing its work. + apiServiceRegistrationControllerInitiated := make(chan struct{}) + if err := genericServer.RegisterMuxAndDiscoveryCompleteSignal("APIServiceRegistrationControllerInitiated", apiServiceRegistrationControllerInitiated); err != nil { + return nil, err + } + + s := &APIAggregator{ + GenericAPIServer: genericServer, + delegateHandler: delegationTarget.UnprotectedHandler(), + proxyTransport: c.ExtraConfig.ProxyTransport, + proxyHandlers: map[string]*proxyHandler{}, + handledGroups: sets.String{}, + lister: informerFactory.Apiregistration().V1().APIServices().Lister(), + APIRegistrationInformers: informerFactory, + serviceResolver: c.ExtraConfig.ServiceResolver, + openAPIConfig: c.GenericConfig.OpenAPIConfig, + openAPIV3Config: c.GenericConfig.OpenAPIV3Config, + egressSelector: c.GenericConfig.EgressSelector, + proxyCurrentCertKeyContent: func() (bytes []byte, bytes2 []byte) { return nil, nil }, + rejectForwardingRedirects: c.ExtraConfig.RejectForwardingRedirects, + } + + // used later to filter the served resource by those that have expired. + resourceExpirationEvaluator, err := genericapiserver.NewResourceExpirationEvaluator(*c.GenericConfig.Version) + if err != nil { + return nil, err + } + + apiGroupInfo := apiservicerest.NewRESTStorage(c.GenericConfig.MergedResourceConfig, c.GenericConfig.RESTOptionsGetter, resourceExpirationEvaluator.ShouldServeForVersion(1, 22)) + if err := s.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil { + return nil, err + } + + enabledVersions := sets.NewString() + for v := range apiGroupInfo.VersionedResourcesStorageMap { + enabledVersions.Insert(v) + } + if !enabledVersions.Has(v1.SchemeGroupVersion.Version) { + return nil, fmt.Errorf("API group/version %s must be enabled", v1.SchemeGroupVersion.String()) + } + + apisHandler := &apisHandler{ + codecs: aggregatorscheme.Codecs, + lister: s.lister, + discoveryGroup: discoveryGroup(enabledVersions), + } + s.GenericAPIServer.Handler.NonGoRestfulMux.Handle("/apis", apisHandler) + s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle("/apis/", apisHandler) + + apiserviceRegistrationController := NewAPIServiceRegistrationController(informerFactory.Apiregistration().V1().APIServices(), s) + if len(c.ExtraConfig.ProxyClientCertFile) > 0 && len(c.ExtraConfig.ProxyClientKeyFile) > 0 { + aggregatorProxyCerts, err := dynamiccertificates.NewDynamicServingContentFromFiles("aggregator-proxy-cert", c.ExtraConfig.ProxyClientCertFile, c.ExtraConfig.ProxyClientKeyFile) + if err != nil { + return nil, err + } + // We are passing the context to ProxyCerts.RunOnce as it needs to implement RunOnce(ctx) however the + // context is not used at all. So passing a empty context shouldn't be a problem + ctx := context.TODO() + if err := aggregatorProxyCerts.RunOnce(ctx); err != nil { + return nil, err + } + aggregatorProxyCerts.AddListener(apiserviceRegistrationController) + s.proxyCurrentCertKeyContent = aggregatorProxyCerts.CurrentCertKeyContent + + s.GenericAPIServer.AddPostStartHookOrDie("aggregator-reload-proxy-client-cert", func(postStartHookContext genericapiserver.PostStartHookContext) error { + // generate a context from stopCh. This is to avoid modifying files which are relying on apiserver + // TODO: See if we can pass ctx to the current method + ctx, cancel := context.WithCancel(context.Background()) + go func() { + select { + case <-postStartHookContext.StopCh: + cancel() // stopCh closed, so cancel our context + case <-ctx.Done(): + } + }() + go aggregatorProxyCerts.Run(ctx, 1) + return nil + }) + } + + availableController, err := statuscontrollers.NewAvailableConditionController( + informerFactory.Apiregistration().V1().APIServices(), + c.GenericConfig.SharedInformerFactory.Core().V1().Services(), + c.GenericConfig.SharedInformerFactory.Core().V1().Endpoints(), + apiregistrationClient.ApiregistrationV1(), + c.ExtraConfig.ProxyTransport, + (func() ([]byte, []byte))(s.proxyCurrentCertKeyContent), + s.serviceResolver, + c.GenericConfig.EgressSelector, + ) + if err != nil { + return nil, err + } + + s.GenericAPIServer.AddPostStartHookOrDie("start-kube-aggregator-informers", func(context genericapiserver.PostStartHookContext) error { + informerFactory.Start(context.StopCh) + c.GenericConfig.SharedInformerFactory.Start(context.StopCh) + return nil + }) + s.GenericAPIServer.AddPostStartHookOrDie("apiservice-registration-controller", func(context genericapiserver.PostStartHookContext) error { + go apiserviceRegistrationController.Run(context.StopCh, apiServiceRegistrationControllerInitiated) + select { + case <-context.StopCh: + case <-apiServiceRegistrationControllerInitiated: + } + + return nil + }) + s.GenericAPIServer.AddPostStartHookOrDie("apiservice-status-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 + }) + + if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.StorageVersionAPI) && + utilfeature.DefaultFeatureGate.Enabled(genericfeatures.APIServerIdentity) { + // Spawn a goroutine in aggregator apiserver to update storage version for + // all built-in resources + s.GenericAPIServer.AddPostStartHookOrDie(StorageVersionPostStartHookName, func(hookContext genericapiserver.PostStartHookContext) error { + // Wait for apiserver-identity to exist first before updating storage + // versions, to avoid storage version GC accidentally garbage-collecting + // storage versions. + kubeClient, err := kubernetes.NewForConfig(hookContext.LoopbackClientConfig) + if err != nil { + return err + } + if err := wait.PollImmediateUntil(100*time.Millisecond, func() (bool, error) { + _, err := kubeClient.CoordinationV1().Leases(metav1.NamespaceSystem).Get( + context.TODO(), s.GenericAPIServer.APIServerID, metav1.GetOptions{}) + if apierrors.IsNotFound(err) { + return false, nil + } + if err != nil { + return false, err + } + return true, nil + }, hookContext.StopCh); err != nil { + return fmt.Errorf("failed to wait for apiserver-identity lease %s to be created: %v", + s.GenericAPIServer.APIServerID, err) + } + // Technically an apiserver only needs to update storage version once during bootstrap. + // Reconcile StorageVersion objects every 10 minutes will help in the case that the + // StorageVersion objects get accidentally modified/deleted by a different agent. In that + // case, the reconciliation ensures future storage migration still works. If nothing gets + // changed, the reconciliation update is a noop and gets short-circuited by the apiserver, + // therefore won't change the resource version and trigger storage migration. + go wait.PollImmediateUntil(10*time.Minute, func() (bool, error) { + // All apiservers (aggregator-apiserver, kube-apiserver, apiextensions-apiserver) + // share the same generic apiserver config. The same StorageVersion manager is used + // to register all built-in resources when the generic apiservers install APIs. + s.GenericAPIServer.StorageVersionManager.UpdateStorageVersions(hookContext.LoopbackClientConfig, s.GenericAPIServer.APIServerID) + return false, nil + }, hookContext.StopCh) + // Once the storage version updater finishes the first round of update, + // the PostStartHook will return to unblock /healthz. The handler chain + // won't block write requests anymore. Check every second since it's not + // expensive. + wait.PollImmediateUntil(1*time.Second, func() (bool, error) { + return s.GenericAPIServer.StorageVersionManager.Completed(), nil + }, hookContext.StopCh) + return nil + }) + } + + return s, nil +} + +// PrepareRun prepares the aggregator to run, by setting up the OpenAPI spec and calling +// the generic PrepareRun. +func (s *APIAggregator) PrepareRun() (preparedAPIAggregator, error) { + // add post start hook before generic PrepareRun in order to be before /healthz installation + if s.openAPIConfig != nil { + s.GenericAPIServer.AddPostStartHookOrDie("apiservice-openapi-controller", func(context genericapiserver.PostStartHookContext) error { + go s.openAPIAggregationController.Run(context.StopCh) + return nil + }) + } + + if s.openAPIV3Config != nil && utilfeature.DefaultFeatureGate.Enabled(genericfeatures.OpenAPIV3) { + s.GenericAPIServer.AddPostStartHookOrDie("apiservice-openapiv3-controller", func(context genericapiserver.PostStartHookContext) error { + go s.openAPIV3AggregationController.Run(context.StopCh) + return nil + }) + } + + prepared := s.GenericAPIServer.PrepareRun() + + // delay OpenAPI setup until the delegate had a chance to setup their OpenAPI handlers + if s.openAPIConfig != nil { + specDownloader := openapiaggregator.NewDownloader() + openAPIAggregator, err := openapiaggregator.BuildAndRegisterAggregator( + &specDownloader, + s.GenericAPIServer.NextDelegate(), + s.GenericAPIServer.Handler.GoRestfulContainer.RegisteredWebServices(), + s.openAPIConfig, + s.GenericAPIServer.Handler.NonGoRestfulMux) + if err != nil { + return preparedAPIAggregator{}, err + } + s.openAPIAggregationController = openapicontroller.NewAggregationController(&specDownloader, openAPIAggregator) + } + + if s.openAPIV3Config != nil && utilfeature.DefaultFeatureGate.Enabled(genericfeatures.OpenAPIV3) { + specDownloaderV3 := openapiv3aggregator.NewDownloader() + openAPIV3Aggregator, err := openapiv3aggregator.BuildAndRegisterAggregator( + specDownloaderV3, + s.GenericAPIServer.NextDelegate(), + s.GenericAPIServer.Handler.NonGoRestfulMux) + if err != nil { + return preparedAPIAggregator{}, err + } + s.openAPIV3AggregationController = openapiv3controller.NewAggregationController(openAPIV3Aggregator) + } + + return preparedAPIAggregator{APIAggregator: s, runnable: prepared}, nil +} + +func (s preparedAPIAggregator) Run(stopCh <-chan struct{}) error { + return s.runnable.Run(stopCh) +} + +// AddAPIService adds an API service. It is not thread-safe, so only call it on one thread at a time please. +// It's a slow moving API, so its ok to run the controller on a single thread +func (s *APIAggregator) AddAPIService(apiService *v1.APIService) error { + // if the proxyHandler already exists, it needs to be updated. The aggregation bits do not + // since they are wired against listers because they require multiple resources to respond + if proxyHandler, exists := s.proxyHandlers[apiService.Name]; exists { + proxyHandler.updateAPIService(apiService) + if s.openAPIAggregationController != nil { + s.openAPIAggregationController.UpdateAPIService(proxyHandler, apiService) + } + if s.openAPIV3AggregationController != nil { + s.openAPIV3AggregationController.UpdateAPIService(proxyHandler, apiService) + } + return nil + } + + proxyPath := "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version + // v1. is a special case for the legacy API. It proxies to a wider set of endpoints. + if apiService.Name == legacyAPIServiceName { + proxyPath = "/api" + } + + // register the proxy handler + proxyHandler := &proxyHandler{ + localDelegate: s.delegateHandler, + proxyCurrentCertKeyContent: s.proxyCurrentCertKeyContent, + proxyTransport: s.proxyTransport, + serviceResolver: s.serviceResolver, + egressSelector: s.egressSelector, + rejectForwardingRedirects: s.rejectForwardingRedirects, + } + proxyHandler.updateAPIService(apiService) + if s.openAPIAggregationController != nil { + s.openAPIAggregationController.AddAPIService(proxyHandler, apiService) + } + if s.openAPIV3AggregationController != nil { + s.openAPIV3AggregationController.AddAPIService(proxyHandler, apiService) + } + s.proxyHandlers[apiService.Name] = proxyHandler + s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(proxyPath, proxyHandler) + s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandlePrefix(proxyPath+"/", proxyHandler) + + // if we're dealing with the legacy group, we're done here + if apiService.Name == legacyAPIServiceName { + return nil + } + + // if we've already registered the path with the handler, we don't want to do it again. + if s.handledGroups.Has(apiService.Spec.Group) { + return nil + } + + // it's time to register the group aggregation endpoint + groupPath := "/apis/" + apiService.Spec.Group + groupDiscoveryHandler := &apiGroupHandler{ + codecs: aggregatorscheme.Codecs, + groupName: apiService.Spec.Group, + lister: s.lister, + delegate: s.delegateHandler, + } + // aggregation is protected + s.GenericAPIServer.Handler.NonGoRestfulMux.Handle(groupPath, groupDiscoveryHandler) + s.GenericAPIServer.Handler.NonGoRestfulMux.UnlistedHandle(groupPath+"/", groupDiscoveryHandler) + s.handledGroups.Insert(apiService.Spec.Group) + return nil +} + +// RemoveAPIService removes the APIService from being handled. It is not thread-safe, so only call it on one thread at a time please. +// It's a slow moving API, so it's ok to run the controller on a single thread. +func (s *APIAggregator) RemoveAPIService(apiServiceName string) { + version := v1helper.APIServiceNameToGroupVersion(apiServiceName) + + proxyPath := "/apis/" + version.Group + "/" + version.Version + // v1. is a special case for the legacy API. It proxies to a wider set of endpoints. + if apiServiceName == legacyAPIServiceName { + proxyPath = "/api" + } + s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath) + s.GenericAPIServer.Handler.NonGoRestfulMux.Unregister(proxyPath + "/") + if s.openAPIAggregationController != nil { + s.openAPIAggregationController.RemoveAPIService(apiServiceName) + } + if s.openAPIV3AggregationController != nil { + s.openAPIAggregationController.RemoveAPIService(apiServiceName) + } + delete(s.proxyHandlers, apiServiceName) + + // TODO unregister group level discovery when there are no more versions for the group + // We don't need this right away because the handler properly delegates when no versions are present +} + +// DefaultAPIResourceConfigSource returns default configuration for an APIResource. +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( + v1.SchemeGroupVersion, + v1beta1.SchemeGroupVersion, + ) + + return ret +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/apiservice_controller.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/apiservice_controller.go new file mode 100644 index 000000000000..52df3cb25fad --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/apiservice_controller.go @@ -0,0 +1,209 @@ +/* +Copyright 2016 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" + "time" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/server/dynamiccertificates" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + informers "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1" + listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1" + "k8s.io/kube-aggregator/pkg/controllers" +) + +// APIHandlerManager defines the behaviour that an API handler should have. +type APIHandlerManager interface { + AddAPIService(apiService *v1.APIService) error + RemoveAPIService(apiServiceName string) +} + +// APIServiceRegistrationController is responsible for registering and removing API services. +type APIServiceRegistrationController struct { + apiHandlerManager APIHandlerManager + + apiServiceLister listers.APIServiceLister + apiServiceSynced cache.InformerSynced + + // To allow injection for testing. + syncFn func(key string) error + + queue workqueue.RateLimitingInterface +} + +var _ dynamiccertificates.Listener = &APIServiceRegistrationController{} + +// NewAPIServiceRegistrationController returns a new APIServiceRegistrationController. +func NewAPIServiceRegistrationController(apiServiceInformer informers.APIServiceInformer, apiHandlerManager APIHandlerManager) *APIServiceRegistrationController { + c := &APIServiceRegistrationController{ + apiHandlerManager: apiHandlerManager, + apiServiceLister: apiServiceInformer.Lister(), + apiServiceSynced: apiServiceInformer.Informer().HasSynced, + queue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), "APIServiceRegistrationController"), + } + + apiServiceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.addAPIService, + UpdateFunc: c.updateAPIService, + DeleteFunc: c.deleteAPIService, + }) + + c.syncFn = c.sync + + return c +} + +func (c *APIServiceRegistrationController) sync(key string) error { + apiService, err := c.apiServiceLister.Get(key) + if apierrors.IsNotFound(err) { + c.apiHandlerManager.RemoveAPIService(key) + return nil + } + if err != nil { + return err + } + + return c.apiHandlerManager.AddAPIService(apiService) +} + +// Run starts APIServiceRegistrationController which will process all registration requests until stopCh is closed. +func (c *APIServiceRegistrationController) Run(stopCh <-chan struct{}, handlerSyncedCh chan<- struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + klog.Info("Starting APIServiceRegistrationController") + defer klog.Info("Shutting down APIServiceRegistrationController") + + if !controllers.WaitForCacheSync("APIServiceRegistrationController", stopCh, c.apiServiceSynced) { + return + } + + /// initially sync all APIServices to make sure the proxy handler is complete + if err := wait.PollImmediateUntil(time.Second, func() (bool, error) { + services, err := c.apiServiceLister.List(labels.Everything()) + if err != nil { + utilruntime.HandleError(fmt.Errorf("failed to initially list APIServices: %v", err)) + return false, nil + } + for _, s := range services { + if err := c.apiHandlerManager.AddAPIService(s); err != nil { + utilruntime.HandleError(fmt.Errorf("failed to initially sync APIService %s: %v", s.Name, err)) + return false, nil + } + } + return true, nil + }, stopCh); err == wait.ErrWaitTimeout { + utilruntime.HandleError(fmt.Errorf("timed out waiting for proxy handler to initialize")) + return + } else if err != nil { + panic(fmt.Errorf("unexpected error: %v", err)) + } + close(handlerSyncedCh) + + // only start one worker thread since its a slow moving API and the aggregation server adding bits + // aren't threadsafe + go wait.Until(c.runWorker, time.Second, stopCh) + + <-stopCh +} + +func (c *APIServiceRegistrationController) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. +func (c *APIServiceRegistrationController) processNextWorkItem() bool { + key, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(key) + + err := c.syncFn(key.(string)) + if err == nil { + c.queue.Forget(key) + return true + } + + utilruntime.HandleError(fmt.Errorf("%v failed with : %v", key, err)) + c.queue.AddRateLimited(key) + + return true +} + +func (c *APIServiceRegistrationController) enqueueInternal(obj *v1.APIService) { + key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj) + if err != nil { + klog.Errorf("Couldn't get key for object %#v: %v", obj, err) + return + } + + c.queue.Add(key) +} + +func (c *APIServiceRegistrationController) addAPIService(obj interface{}) { + castObj := obj.(*v1.APIService) + klog.V(4).Infof("Adding %s", castObj.Name) + c.enqueueInternal(castObj) +} + +func (c *APIServiceRegistrationController) updateAPIService(obj, _ interface{}) { + castObj := obj.(*v1.APIService) + klog.V(4).Infof("Updating %s", castObj.Name) + c.enqueueInternal(castObj) +} + +func (c *APIServiceRegistrationController) deleteAPIService(obj interface{}) { + castObj, ok := obj.(*v1.APIService) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + klog.Errorf("Couldn't get object from tombstone %#v", obj) + return + } + castObj, ok = tombstone.Obj.(*v1.APIService) + if !ok { + klog.Errorf("Tombstone contained object that is not expected %#v", obj) + return + } + } + klog.V(4).Infof("Deleting %q", castObj.Name) + c.enqueueInternal(castObj) +} + +// Enqueue queues all apiservices to be rehandled. +// This method is used by the controller to notify when the proxy cert content changes. +func (c *APIServiceRegistrationController) Enqueue() { + apiServices, err := c.apiServiceLister.List(labels.Everything()) + if err != nil { + utilruntime.HandleError(err) + return + } + for _, apiService := range apiServices { + c.addAPIService(apiService) + } +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go new file mode 100644 index 000000000000..c014e044aad7 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/handler_apis.go @@ -0,0 +1,166 @@ +/* +Copyright 2016 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 ( + "net/http" + + apierrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apiserver/pkg/endpoints/handlers/negotiation" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + + apiregistrationv1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" + apiregistrationv1beta1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" + listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1" +) + +// apisHandler serves the `/apis` endpoint. +// This is registered as a filter so that it never collides with any explicitly registered endpoints +type apisHandler struct { + codecs serializer.CodecFactory + lister listers.APIServiceLister + discoveryGroup metav1.APIGroup +} + +func discoveryGroup(enabledVersions sets.String) metav1.APIGroup { + retval := metav1.APIGroup{ + Name: apiregistrationv1api.GroupName, + Versions: []metav1.GroupVersionForDiscovery{ + { + GroupVersion: apiregistrationv1api.SchemeGroupVersion.String(), + Version: apiregistrationv1api.SchemeGroupVersion.Version, + }, + }, + PreferredVersion: metav1.GroupVersionForDiscovery{ + GroupVersion: apiregistrationv1api.SchemeGroupVersion.String(), + Version: apiregistrationv1api.SchemeGroupVersion.Version, + }, + } + + if enabledVersions.Has(apiregistrationv1beta1api.SchemeGroupVersion.Version) { + retval.Versions = append(retval.Versions, metav1.GroupVersionForDiscovery{ + GroupVersion: apiregistrationv1beta1api.SchemeGroupVersion.String(), + Version: apiregistrationv1beta1api.SchemeGroupVersion.Version, + }) + } + + return retval +} + +func (r *apisHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + discoveryGroupList := &metav1.APIGroupList{ + // always add OUR api group to the list first. Since we'll never have a registered APIService for it + // and since this is the crux of the API, having this first will give our names priority. It's good to be king. + Groups: []metav1.APIGroup{r.discoveryGroup}, + } + + apiServices, err := r.lister.List(labels.Everything()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + apiServicesByGroup := apiregistrationv1apihelper.SortedByGroupAndVersion(apiServices) + for _, apiGroupServers := range apiServicesByGroup { + // skip the legacy group + if len(apiGroupServers[0].Spec.Group) == 0 { + continue + } + discoveryGroup := convertToDiscoveryAPIGroup(apiGroupServers) + if discoveryGroup != nil { + discoveryGroupList.Groups = append(discoveryGroupList.Groups, *discoveryGroup) + } + } + + responsewriters.WriteObjectNegotiated(r.codecs, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroupList) +} + +// convertToDiscoveryAPIGroup takes apiservices in a single group and returns a discovery compatible object. +// if none of the services are available, it will return nil. +func convertToDiscoveryAPIGroup(apiServices []*apiregistrationv1api.APIService) *metav1.APIGroup { + apiServicesByGroup := apiregistrationv1apihelper.SortedByGroupAndVersion(apiServices)[0] + + var discoveryGroup *metav1.APIGroup + + for _, apiService := range apiServicesByGroup { + // the first APIService which is valid becomes the default + if discoveryGroup == nil { + discoveryGroup = &metav1.APIGroup{ + Name: apiService.Spec.Group, + PreferredVersion: metav1.GroupVersionForDiscovery{ + GroupVersion: apiService.Spec.Group + "/" + apiService.Spec.Version, + Version: apiService.Spec.Version, + }, + } + } + + discoveryGroup.Versions = append(discoveryGroup.Versions, + metav1.GroupVersionForDiscovery{ + GroupVersion: apiService.Spec.Group + "/" + apiService.Spec.Version, + Version: apiService.Spec.Version, + }, + ) + } + + return discoveryGroup +} + +// apiGroupHandler serves the `/apis/` endpoint. +type apiGroupHandler struct { + codecs serializer.CodecFactory + groupName string + + lister listers.APIServiceLister + + delegate http.Handler +} + +func (r *apiGroupHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + apiServices, err := r.lister.List(labels.Everything()) + if statusErr, ok := err.(*apierrors.StatusError); ok { + responsewriters.WriteRawJSON(int(statusErr.Status().Code), statusErr.Status(), w) + return + } + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + apiServicesForGroup := []*apiregistrationv1api.APIService{} + for _, apiService := range apiServices { + if apiService.Spec.Group == r.groupName { + apiServicesForGroup = append(apiServicesForGroup, apiService) + } + } + + if len(apiServicesForGroup) == 0 { + r.delegate.ServeHTTP(w, req) + return + } + + discoveryGroup := convertToDiscoveryAPIGroup(apiServicesForGroup) + if discoveryGroup == nil { + http.Error(w, "", http.StatusNotFound) + return + } + responsewriters.WriteObjectNegotiated(r.codecs, negotiation.DefaultEndpointRestrictions, schema.GroupVersion{}, w, req, http.StatusOK, discoveryGroup) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go new file mode 100644 index 000000000000..e1282f2ab131 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go @@ -0,0 +1,280 @@ +/* +Copyright 2016 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 ( + "context" + "net/http" + "net/url" + "strings" + "sync/atomic" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/httpstream" + utilnet "k8s.io/apimachinery/pkg/util/net" + "k8s.io/apimachinery/pkg/util/proxy" + auditinternal "k8s.io/apiserver/pkg/apis/audit" + "k8s.io/apiserver/pkg/endpoints/handlers/responsewriters" + endpointmetrics "k8s.io/apiserver/pkg/endpoints/metrics" + genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/server/egressselector" + utilflowcontrol "k8s.io/apiserver/pkg/util/flowcontrol" + "k8s.io/apiserver/pkg/util/x509metrics" + restclient "k8s.io/client-go/rest" + "k8s.io/client-go/transport" + "k8s.io/klog/v2" + apiregistrationv1api "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" +) + +const ( + aggregatorComponent string = "aggregator" + + aggregatedDiscoveryTimeout = 5 * time.Second +) + +type certKeyFunc func() ([]byte, []byte) + +// proxyHandler provides a http.Handler which will proxy traffic to locations +// specified by items implementing Redirector. +type proxyHandler struct { + // localDelegate is used to satisfy local APIServices + localDelegate http.Handler + + // proxyCurrentCertKeyContent holds the client cert used to identify this proxy. Backing APIServices use this to confirm the proxy's identity + proxyCurrentCertKeyContent certKeyFunc + proxyTransport *http.Transport + + // Endpoints based routing to map from cluster IP to routable IP + serviceResolver ServiceResolver + + handlingInfo atomic.Value + + // egressSelector selects the proper egress dialer to communicate with the custom apiserver + // overwrites proxyTransport dialer if not nil + egressSelector *egressselector.EgressSelector + + // reject to forward redirect response + rejectForwardingRedirects bool +} + +type proxyHandlingInfo struct { + // local indicates that this APIService is locally satisfied + local bool + + // name is the name of the APIService + name string + // restConfig holds the information for building a roundtripper + restConfig *restclient.Config + // transportBuildingError is an error produced while building the transport. If this + // is non-nil, it will be reported to clients. + transportBuildingError error + // proxyRoundTripper is the re-useable portion of the transport. It does not vary with any request. + proxyRoundTripper http.RoundTripper + // serviceName is the name of the service this handler proxies to + serviceName string + // namespace is the namespace the service lives in + serviceNamespace string + // serviceAvailable indicates this APIService is available or not + serviceAvailable bool + // servicePort is the port of the service this handler proxies to + servicePort int32 +} + +func proxyError(w http.ResponseWriter, req *http.Request, error string, code int) { + http.Error(w, error, code) + + ctx := req.Context() + info, ok := genericapirequest.RequestInfoFrom(ctx) + if !ok { + klog.Warning("no RequestInfo found in the context") + return + } + // TODO: record long-running request differently? The long-running check func does not necessarily match the one of the aggregated apiserver + endpointmetrics.RecordRequestTermination(req, info, aggregatorComponent, code) +} + +func (r *proxyHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + value := r.handlingInfo.Load() + if value == nil { + r.localDelegate.ServeHTTP(w, req) + return + } + handlingInfo := value.(proxyHandlingInfo) + if handlingInfo.local { + if r.localDelegate == nil { + http.Error(w, "", http.StatusNotFound) + return + } + r.localDelegate.ServeHTTP(w, req) + return + } + + if !handlingInfo.serviceAvailable { + proxyError(w, req, "service unavailable", http.StatusServiceUnavailable) + return + } + + if handlingInfo.transportBuildingError != nil { + proxyError(w, req, handlingInfo.transportBuildingError.Error(), http.StatusInternalServerError) + return + } + + user, ok := genericapirequest.UserFrom(req.Context()) + if !ok { + proxyError(w, req, "missing user", http.StatusInternalServerError) + return + } + + // write a new location based on the existing request pointed at the target service + location := &url.URL{} + location.Scheme = "https" + rloc, err := r.serviceResolver.ResolveEndpoint(handlingInfo.serviceNamespace, handlingInfo.serviceName, handlingInfo.servicePort) + if err != nil { + klog.Errorf("error resolving %s/%s: %v", handlingInfo.serviceNamespace, handlingInfo.serviceName, err) + proxyError(w, req, "service unavailable", http.StatusServiceUnavailable) + return + } + location.Host = rloc.Host + location.Path = req.URL.Path + location.RawQuery = req.URL.Query().Encode() + + newReq, cancelFn := newRequestForProxy(location, req) + defer cancelFn() + + if handlingInfo.proxyRoundTripper == nil { + proxyError(w, req, "", http.StatusNotFound) + return + } + + proxyRoundTripper := handlingInfo.proxyRoundTripper + upgrade := httpstream.IsUpgradeRequest(req) + + proxyRoundTripper = transport.NewAuthProxyRoundTripper(user.GetName(), user.GetGroups(), user.GetExtra(), proxyRoundTripper) + + // If we are upgrading, then the upgrade path tries to use this request with the TLS config we provide, but it does + // NOT use the proxyRoundTripper. It's a direct dial that bypasses the proxyRoundTripper. This means that we have to + // attach the "correct" user headers to the request ahead of time. + if upgrade { + transport.SetAuthProxyHeaders(newReq, user.GetName(), user.GetGroups(), user.GetExtra()) + } + + handler := proxy.NewUpgradeAwareHandler(location, proxyRoundTripper, true, upgrade, &responder{w: w}) + if r.rejectForwardingRedirects { + handler.RejectForwardingRedirects = true + } + utilflowcontrol.RequestDelegated(req.Context()) + handler.ServeHTTP(w, newReq) +} + +// newRequestForProxy returns a shallow copy of the original request with a context that may include a timeout for discovery requests +func newRequestForProxy(location *url.URL, req *http.Request) (*http.Request, context.CancelFunc) { + newCtx := req.Context() + cancelFn := func() {} + + if requestInfo, ok := genericapirequest.RequestInfoFrom(req.Context()); ok { + // trim leading and trailing slashes. Then "/apis/group/version" requests are for discovery, so if we have exactly three + // segments that we are going to proxy, we have a discovery request. + if !requestInfo.IsResourceRequest && len(strings.Split(strings.Trim(requestInfo.Path, "/"), "/")) == 3 { + // discovery requests are used by kubectl and others to determine which resources a server has. This is a cheap call that + // should be fast for every aggregated apiserver. Latency for aggregation is expected to be low (as for all extensions) + // so forcing a short timeout here helps responsiveness of all clients. + newCtx, cancelFn = context.WithTimeout(newCtx, aggregatedDiscoveryTimeout) + } + } + + // WithContext creates a shallow clone of the request with the same context. + newReq := req.WithContext(newCtx) + newReq.Header = utilnet.CloneHeader(req.Header) + newReq.URL = location + newReq.Host = location.Host + + // If the original request has an audit ID, let's make sure we propagate this + // to the aggregated server. + if auditID, found := genericapirequest.AuditIDFrom(req.Context()); found { + newReq.Header.Set(auditinternal.HeaderAuditID, string(auditID)) + } + + return newReq, cancelFn +} + +// responder implements rest.Responder for assisting a connector in writing objects or errors. +type responder struct { + w http.ResponseWriter +} + +// TODO this should properly handle content type negotiation +// if the caller asked for protobuf and you write JSON bad things happen. +func (r *responder) Object(statusCode int, obj runtime.Object) { + responsewriters.WriteRawJSON(statusCode, obj, r.w) +} + +func (r *responder) Error(_ http.ResponseWriter, _ *http.Request, err error) { + http.Error(r.w, err.Error(), http.StatusServiceUnavailable) +} + +// these methods provide locked access to fields + +func (r *proxyHandler) updateAPIService(apiService *apiregistrationv1api.APIService) { + if apiService.Spec.Service == nil { + r.handlingInfo.Store(proxyHandlingInfo{local: true}) + return + } + + proxyClientCert, proxyClientKey := r.proxyCurrentCertKeyContent() + + clientConfig := &restclient.Config{ + TLSClientConfig: restclient.TLSClientConfig{ + Insecure: apiService.Spec.InsecureSkipTLSVerify, + ServerName: apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc", + CertData: proxyClientCert, + KeyData: proxyClientKey, + CAData: apiService.Spec.CABundle, + }, + } + clientConfig.Wrap(x509metrics.NewDeprecatedCertificateRoundTripperWrapperConstructor( + x509MissingSANCounter, + x509InsecureSHA1Counter, + )) + + newInfo := proxyHandlingInfo{ + name: apiService.Name, + restConfig: clientConfig, + serviceName: apiService.Spec.Service.Name, + serviceNamespace: apiService.Spec.Service.Namespace, + servicePort: *apiService.Spec.Service.Port, + serviceAvailable: apiregistrationv1apihelper.IsAPIServiceConditionTrue(apiService, apiregistrationv1api.Available), + } + if r.egressSelector != nil { + networkContext := egressselector.Cluster.AsNetworkContext() + var egressDialer utilnet.DialFunc + egressDialer, err := r.egressSelector.Lookup(networkContext) + if err != nil { + klog.Warning(err.Error()) + } else { + newInfo.restConfig.Dial = egressDialer + } + } else if r.proxyTransport != nil && r.proxyTransport.DialContext != nil { + newInfo.restConfig.Dial = r.proxyTransport.DialContext + } + newInfo.proxyRoundTripper, newInfo.transportBuildingError = restclient.TransportFor(newInfo.restConfig) + if newInfo.transportBuildingError != nil { + klog.Warning(newInfo.transportBuildingError.Error()) + } + r.handlingInfo.Store(newInfo) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/metrics.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/metrics.go new file mode 100644 index 000000000000..03315e984ff8 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/metrics.go @@ -0,0 +1,52 @@ +/* +Copyright 2020 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 ( + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +var x509MissingSANCounter = metrics.NewCounter( + &metrics.CounterOpts{ + Subsystem: "kube_aggregator", + Namespace: "apiserver", + Name: "x509_missing_san_total", + Help: "Counts the number of requests to servers missing SAN extension " + + "in their serving certificate OR the number of connection failures " + + "due to the lack of x509 certificate SAN extension missing " + + "(either/or, based on the runtime environment)", + StabilityLevel: metrics.ALPHA, + }, +) + +var x509InsecureSHA1Counter = metrics.NewCounter( + &metrics.CounterOpts{ + Subsystem: "kube_aggregator", + Namespace: "apiserver", + Name: "x509_insecure_sha1_total", + Help: "Counts the number of requests to servers with insecure SHA1 signatures " + + "in their serving certificate OR the number of connection failures " + + "due to the insecure SHA1 signatures (either/or, based on the runtime environment)", + StabilityLevel: metrics.ALPHA, + }, +) + +func init() { + legacyregistry.MustRegister(x509MissingSANCounter) + legacyregistry.MustRegister(x509InsecureSHA1Counter) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/resolvers.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/resolvers.go new file mode 100644 index 000000000000..74bcb24d9876 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/resolvers.go @@ -0,0 +1,84 @@ +/* +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 ( + "net/url" + + "k8s.io/apiserver/pkg/util/proxy" + listersv1 "k8s.io/client-go/listers/core/v1" +) + +// A ServiceResolver knows how to get a URL given a service. +type ServiceResolver interface { + ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) +} + +// NewEndpointServiceResolver returns a ServiceResolver that chooses one of the +// service's endpoints. +func NewEndpointServiceResolver(services listersv1.ServiceLister, endpoints listersv1.EndpointsLister) ServiceResolver { + return &aggregatorEndpointRouting{ + services: services, + endpoints: endpoints, + } +} + +type aggregatorEndpointRouting struct { + services listersv1.ServiceLister + endpoints listersv1.EndpointsLister +} + +func (r *aggregatorEndpointRouting) ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) { + return proxy.ResolveEndpoint(r.services, r.endpoints, namespace, name, port) +} + +// NewClusterIPServiceResolver returns a ServiceResolver that directly calls the +// service's cluster IP. +func NewClusterIPServiceResolver(services listersv1.ServiceLister) ServiceResolver { + return &aggregatorClusterRouting{ + services: services, + } +} + +type aggregatorClusterRouting struct { + services listersv1.ServiceLister +} + +func (r *aggregatorClusterRouting) ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) { + return proxy.ResolveCluster(r.services, namespace, name, port) +} + +// NewLoopbackServiceResolver returns a ServiceResolver that routes +// the kubernetes/default service with port 443 to loopback. +func NewLoopbackServiceResolver(delegate ServiceResolver, host *url.URL) ServiceResolver { + return &loopbackResolver{ + delegate: delegate, + host: host, + } +} + +type loopbackResolver struct { + delegate ServiceResolver + host *url.URL +} + +func (r *loopbackResolver) ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) { + if namespace == "default" && name == "kubernetes" && port == 443 { + return r.host, nil + } + return r.delegate.ResolveEndpoint(namespace, name, port) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/apiserver/scheme/scheme.go b/vendor/k8s.io/kube-aggregator/pkg/apiserver/scheme/scheme.go new file mode 100644 index 000000000000..64660f444309 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/apiserver/scheme/scheme.go @@ -0,0 +1,48 @@ +/* +Copyright 2018 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 scheme + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + + "k8s.io/kube-aggregator/pkg/apis/apiregistration" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/install" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" +) + +var ( + // Scheme defines methods for serializing and deserializing API objects. + Scheme = runtime.NewScheme() + // Codecs provides methods for retrieving codecs and serializers for specific + // versions and content types. + Codecs = serializer.NewCodecFactory(Scheme) +) + +func init() { + AddToScheme(Scheme) + install.Install(Scheme) +} + +// AddToScheme adds the types of this group into the given scheme. +func AddToScheme(scheme *runtime.Scheme) { + utilruntime.Must(v1beta1.AddToScheme(scheme)) + utilruntime.Must(v1.AddToScheme(scheme)) + utilruntime.Must(apiregistration.AddToScheme(scheme)) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/interface.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/interface.go new file mode 100644 index 000000000000..55f854a662c5 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/interface.go @@ -0,0 +1,54 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package apiregistration + +import ( + v1 "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1" + v1beta1 "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1" + internalinterfaces "k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to each of this group's versions. +type Interface interface { + // V1beta1 provides access to shared informers for resources in V1beta1. + V1beta1() v1beta1.Interface + // V1 provides access to shared informers for resources in V1. + V1() v1.Interface +} + +type group struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// V1beta1 returns a new v1beta1.Interface. +func (g *group) V1beta1() v1beta1.Interface { + return v1beta1.New(g.factory, g.namespace, g.tweakListOptions) +} + +// V1 returns a new v1.Interface. +func (g *group) V1() v1.Interface { + return v1.New(g.factory, g.namespace, g.tweakListOptions) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1/apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1/apiservice.go new file mode 100644 index 000000000000..1c6a5cf20bd6 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1/apiservice.go @@ -0,0 +1,89 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + "context" + time "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + clientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" + internalinterfaces "k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces" + v1 "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1" +) + +// APIServiceInformer provides access to a shared informer and lister for +// APIServices. +type APIServiceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1.APIServiceLister +} + +type aPIServiceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewAPIServiceInformer constructs a new informer for APIService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewAPIServiceInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredAPIServiceInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredAPIServiceInformer constructs a new informer for APIService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredAPIServiceInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ApiregistrationV1().APIServices().List(context.TODO(), options) + }, + WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ApiregistrationV1().APIServices().Watch(context.TODO(), options) + }, + }, + &apiregistrationv1.APIService{}, + resyncPeriod, + indexers, + ) +} + +func (f *aPIServiceInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredAPIServiceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *aPIServiceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiregistrationv1.APIService{}, f.defaultInformer) +} + +func (f *aPIServiceInformer) Lister() v1.APIServiceLister { + return v1.NewAPIServiceLister(f.Informer().GetIndexer()) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1/interface.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1/interface.go new file mode 100644 index 000000000000..d0cf786efe6b --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1/interface.go @@ -0,0 +1,45 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1 + +import ( + internalinterfaces "k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // APIServices returns a APIServiceInformer. + APIServices() APIServiceInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// APIServices returns a APIServiceInformer. +func (v *version) APIServices() APIServiceInformer { + return &aPIServiceInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1/apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1/apiservice.go new file mode 100644 index 000000000000..aab505a25962 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1/apiservice.go @@ -0,0 +1,89 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "context" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" + apiregistrationv1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" + clientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" + internalinterfaces "k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces" + v1beta1 "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1" +) + +// APIServiceInformer provides access to a shared informer and lister for +// APIServices. +type APIServiceInformer interface { + Informer() cache.SharedIndexInformer + Lister() v1beta1.APIServiceLister +} + +type aPIServiceInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// NewAPIServiceInformer constructs a new informer for APIService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewAPIServiceInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredAPIServiceInformer(client, resyncPeriod, indexers, nil) +} + +// NewFilteredAPIServiceInformer constructs a new informer for APIService type. +// Always prefer using an informer factory to get a shared informer instead of getting an independent +// one. This reduces memory footprint and number of connections to the server. +func NewFilteredAPIServiceInformer(client clientset.Interface, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer { + return cache.NewSharedIndexInformer( + &cache.ListWatch{ + ListFunc: func(options v1.ListOptions) (runtime.Object, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ApiregistrationV1beta1().APIServices().List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.ApiregistrationV1beta1().APIServices().Watch(context.TODO(), options) + }, + }, + &apiregistrationv1beta1.APIService{}, + resyncPeriod, + indexers, + ) +} + +func (f *aPIServiceInformer) defaultInformer(client clientset.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredAPIServiceInformer(client, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *aPIServiceInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&apiregistrationv1beta1.APIService{}, f.defaultInformer) +} + +func (f *aPIServiceInformer) Lister() v1beta1.APIServiceLister { + return v1beta1.NewAPIServiceLister(f.Informer().GetIndexer()) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1/interface.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1/interface.go new file mode 100644 index 000000000000..d9f6fc380ef1 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1/interface.go @@ -0,0 +1,45 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package v1beta1 + +import ( + internalinterfaces "k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces" +) + +// Interface provides access to all the informers in this group version. +type Interface interface { + // APIServices returns a APIServiceInformer. + APIServices() APIServiceInformer +} + +type version struct { + factory internalinterfaces.SharedInformerFactory + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc +} + +// New returns a new Interface. +func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface { + return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} +} + +// APIServices returns a APIServiceInformer. +func (v *version) APIServices() APIServiceInformer { + return &aPIServiceInformer{factory: v.factory, tweakListOptions: v.tweakListOptions} +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/factory.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/factory.go new file mode 100644 index 000000000000..88cb2bdecf3a --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/factory.go @@ -0,0 +1,180 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + reflect "reflect" + sync "sync" + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + clientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" + apiregistration "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration" + internalinterfaces "k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces" +) + +// SharedInformerOption defines the functional option type for SharedInformerFactory. +type SharedInformerOption func(*sharedInformerFactory) *sharedInformerFactory + +type sharedInformerFactory struct { + client clientset.Interface + namespace string + tweakListOptions internalinterfaces.TweakListOptionsFunc + lock sync.Mutex + defaultResync time.Duration + customResync map[reflect.Type]time.Duration + + informers map[reflect.Type]cache.SharedIndexInformer + // startedInformers is used for tracking which informers have been started. + // This allows Start() to be called multiple times safely. + startedInformers map[reflect.Type]bool +} + +// WithCustomResyncConfig sets a custom resync period for the specified informer types. +func WithCustomResyncConfig(resyncConfig map[v1.Object]time.Duration) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + for k, v := range resyncConfig { + factory.customResync[reflect.TypeOf(k)] = v + } + return factory + } +} + +// WithTweakListOptions sets a custom filter on all listers of the configured SharedInformerFactory. +func WithTweakListOptions(tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.tweakListOptions = tweakListOptions + return factory + } +} + +// WithNamespace limits the SharedInformerFactory to the specified namespace. +func WithNamespace(namespace string) SharedInformerOption { + return func(factory *sharedInformerFactory) *sharedInformerFactory { + factory.namespace = namespace + return factory + } +} + +// NewSharedInformerFactory constructs a new instance of sharedInformerFactory for all namespaces. +func NewSharedInformerFactory(client clientset.Interface, defaultResync time.Duration) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync) +} + +// NewFilteredSharedInformerFactory constructs a new instance of sharedInformerFactory. +// Listers obtained via this SharedInformerFactory will be subject to the same filters +// as specified here. +// Deprecated: Please use NewSharedInformerFactoryWithOptions instead +func NewFilteredSharedInformerFactory(client clientset.Interface, defaultResync time.Duration, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) SharedInformerFactory { + return NewSharedInformerFactoryWithOptions(client, defaultResync, WithNamespace(namespace), WithTweakListOptions(tweakListOptions)) +} + +// NewSharedInformerFactoryWithOptions constructs a new instance of a SharedInformerFactory with additional options. +func NewSharedInformerFactoryWithOptions(client clientset.Interface, defaultResync time.Duration, options ...SharedInformerOption) SharedInformerFactory { + factory := &sharedInformerFactory{ + client: client, + namespace: v1.NamespaceAll, + defaultResync: defaultResync, + informers: make(map[reflect.Type]cache.SharedIndexInformer), + startedInformers: make(map[reflect.Type]bool), + customResync: make(map[reflect.Type]time.Duration), + } + + // Apply all options + for _, opt := range options { + factory = opt(factory) + } + + return factory +} + +// Start initializes all requested informers. +func (f *sharedInformerFactory) Start(stopCh <-chan struct{}) { + f.lock.Lock() + defer f.lock.Unlock() + + for informerType, informer := range f.informers { + if !f.startedInformers[informerType] { + go informer.Run(stopCh) + f.startedInformers[informerType] = true + } + } +} + +// WaitForCacheSync waits for all started informers' cache were synced. +func (f *sharedInformerFactory) WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool { + informers := func() map[reflect.Type]cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informers := map[reflect.Type]cache.SharedIndexInformer{} + for informerType, informer := range f.informers { + if f.startedInformers[informerType] { + informers[informerType] = informer + } + } + return informers + }() + + res := map[reflect.Type]bool{} + for informType, informer := range informers { + res[informType] = cache.WaitForCacheSync(stopCh, informer.HasSynced) + } + return res +} + +// InternalInformerFor returns the SharedIndexInformer for obj using an internal +// client. +func (f *sharedInformerFactory) InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer { + f.lock.Lock() + defer f.lock.Unlock() + + informerType := reflect.TypeOf(obj) + informer, exists := f.informers[informerType] + if exists { + return informer + } + + resyncPeriod, exists := f.customResync[informerType] + if !exists { + resyncPeriod = f.defaultResync + } + + informer = newFunc(f.client, resyncPeriod) + f.informers[informerType] = informer + + return informer +} + +// SharedInformerFactory provides shared informers for resources in all known +// API group versions. +type SharedInformerFactory interface { + internalinterfaces.SharedInformerFactory + ForResource(resource schema.GroupVersionResource) (GenericInformer, error) + WaitForCacheSync(stopCh <-chan struct{}) map[reflect.Type]bool + + Apiregistration() apiregistration.Interface +} + +func (f *sharedInformerFactory) Apiregistration() apiregistration.Interface { + return apiregistration.New(f, f.namespace, f.tweakListOptions) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/generic.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/generic.go new file mode 100644 index 000000000000..1c89767b2733 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/generic.go @@ -0,0 +1,67 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package externalversions + +import ( + "fmt" + + schema "k8s.io/apimachinery/pkg/runtime/schema" + cache "k8s.io/client-go/tools/cache" + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + v1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" +) + +// GenericInformer is type of SharedIndexInformer which will locate and delegate to other +// sharedInformers based on type +type GenericInformer interface { + Informer() cache.SharedIndexInformer + Lister() cache.GenericLister +} + +type genericInformer struct { + informer cache.SharedIndexInformer + resource schema.GroupResource +} + +// Informer returns the SharedIndexInformer. +func (f *genericInformer) Informer() cache.SharedIndexInformer { + return f.informer +} + +// Lister returns the GenericLister. +func (f *genericInformer) Lister() cache.GenericLister { + return cache.NewGenericLister(f.Informer().GetIndexer(), f.resource) +} + +// ForResource gives generic access to a shared informer of the matching type +// TODO extend this to unknown resources with a client pool +func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { + switch resource { + // Group=apiregistration.k8s.io, Version=v1 + case v1.SchemeGroupVersion.WithResource("apiservices"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Apiregistration().V1().APIServices().Informer()}, nil + + // Group=apiregistration.k8s.io, Version=v1beta1 + case v1beta1.SchemeGroupVersion.WithResource("apiservices"): + return &genericInformer{resource: resource.GroupResource(), informer: f.Apiregistration().V1beta1().APIServices().Informer()}, nil + + } + + return nil, fmt.Errorf("no informer found for %v", resource) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go new file mode 100644 index 000000000000..5fd2bca8ce64 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces/factory_interfaces.go @@ -0,0 +1,40 @@ +/* +Copyright 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. +*/ + +// Code generated by informer-gen. DO NOT EDIT. + +package internalinterfaces + +import ( + time "time" + + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + cache "k8s.io/client-go/tools/cache" + clientset "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset" +) + +// NewInformerFunc takes clientset.Interface and time.Duration to return a SharedIndexInformer. +type NewInformerFunc func(clientset.Interface, time.Duration) cache.SharedIndexInformer + +// SharedInformerFactory a small interface to allow for adding an informer without an import cycle +type SharedInformerFactory interface { + Start(stopCh <-chan struct{}) + InformerFor(obj runtime.Object, newFunc NewInformerFunc) cache.SharedIndexInformer +} + +// TweakListOptionsFunc is a function that transforms a v1.ListOptions. +type TweakListOptionsFunc func(*v1.ListOptions) diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1/apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1/apiservice.go new file mode 100644 index 000000000000..5af77c7f7604 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1/apiservice.go @@ -0,0 +1,68 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" +) + +// APIServiceLister helps list APIServices. +// All objects returned here must be treated as read-only. +type APIServiceLister interface { + // List lists all APIServices in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1.APIService, err error) + // Get retrieves the APIService from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1.APIService, error) + APIServiceListerExpansion +} + +// aPIServiceLister implements the APIServiceLister interface. +type aPIServiceLister struct { + indexer cache.Indexer +} + +// NewAPIServiceLister returns a new APIServiceLister. +func NewAPIServiceLister(indexer cache.Indexer) APIServiceLister { + return &aPIServiceLister{indexer: indexer} +} + +// List lists all APIServices in the indexer. +func (s *aPIServiceLister) List(selector labels.Selector) (ret []*v1.APIService, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1.APIService)) + }) + return ret, err +} + +// Get retrieves the APIService from the index for a given name. +func (s *aPIServiceLister) Get(name string) (*v1.APIService, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1.Resource("apiservice"), name) + } + return obj.(*v1.APIService), nil +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1/expansion_generated.go b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1/expansion_generated.go new file mode 100644 index 000000000000..07d5e73b9ce0 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1/expansion_generated.go @@ -0,0 +1,23 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1 + +// APIServiceListerExpansion allows custom methods to be added to +// APIServiceLister. +type APIServiceListerExpansion interface{} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1/apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1/apiservice.go new file mode 100644 index 000000000000..8628b80d03ec --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1/apiservice.go @@ -0,0 +1,68 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +import ( + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" + v1beta1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1" +) + +// APIServiceLister helps list APIServices. +// All objects returned here must be treated as read-only. +type APIServiceLister interface { + // List lists all APIServices in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v1beta1.APIService, err error) + // Get retrieves the APIService from the index for a given name. + // Objects returned here must be treated as read-only. + Get(name string) (*v1beta1.APIService, error) + APIServiceListerExpansion +} + +// aPIServiceLister implements the APIServiceLister interface. +type aPIServiceLister struct { + indexer cache.Indexer +} + +// NewAPIServiceLister returns a new APIServiceLister. +func NewAPIServiceLister(indexer cache.Indexer) APIServiceLister { + return &aPIServiceLister{indexer: indexer} +} + +// List lists all APIServices in the indexer. +func (s *aPIServiceLister) List(selector labels.Selector) (ret []*v1beta1.APIService, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v1beta1.APIService)) + }) + return ret, err +} + +// Get retrieves the APIService from the index for a given name. +func (s *aPIServiceLister) Get(name string) (*v1beta1.APIService, error) { + obj, exists, err := s.indexer.GetByKey(name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v1beta1.Resource("apiservice"), name) + } + return obj.(*v1beta1.APIService), nil +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1/expansion_generated.go b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1/expansion_generated.go new file mode 100644 index 000000000000..6c7d75f618c9 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1/expansion_generated.go @@ -0,0 +1,23 @@ +/* +Copyright 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. +*/ + +// Code generated by lister-gen. DO NOT EDIT. + +package v1beta1 + +// APIServiceListerExpansion allows custom methods to be added to +// APIServiceLister. +type APIServiceListerExpansion interface{} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/cache.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/cache.go new file mode 100644 index 000000000000..5682d3482fde --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/cache.go @@ -0,0 +1,41 @@ +/* +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 controllers + +import ( + "fmt" + + "k8s.io/klog/v2" + + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/client-go/tools/cache" +) + +// WaitForCacheSync is a wrapper around cache.WaitForCacheSync that generates log messages +// indicating that the controller identified by controllerName is waiting for syncs, followed by +// either a successful or failed sync. +func WaitForCacheSync(controllerName string, stopCh <-chan struct{}, cacheSyncs ...cache.InformerSynced) bool { + klog.Infof("Waiting for caches to sync for %s controller", controllerName) + + if !cache.WaitForCacheSync(stopCh, cacheSyncs...) { + utilruntime.HandleError(fmt.Errorf("Unable to sync caches for %s controller", controllerName)) + return false + } + + klog.Infof("Caches are synced for %s controller", controllerName) + return true +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/aggregator.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/aggregator.go new file mode 100644 index 000000000000..c087fa533914 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/aggregator.go @@ -0,0 +1,351 @@ +/* +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 aggregator + +import ( + "fmt" + "net/http" + "strings" + "sync" + "time" + + restful "github.com/emicklei/go-restful/v3" + + "k8s.io/klog/v2" + + "k8s.io/apiserver/pkg/server" + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/kube-openapi/pkg/aggregator" + "k8s.io/kube-openapi/pkg/builder" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/handler" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +// SpecAggregator calls out to http handlers of APIServices and merges specs. It keeps state of the last +// known specs including the http etag. +type SpecAggregator interface { + AddUpdateAPIService(handler http.Handler, apiService *v1.APIService) error + UpdateAPIServiceSpec(apiServiceName string, spec *spec.Swagger, etag string) error + RemoveAPIServiceSpec(apiServiceName string) error + GetAPIServiceInfo(apiServiceName string) (handler http.Handler, etag string, exists bool) + GetAPIServiceNames() []string +} + +const ( + aggregatorUser = "system:aggregator" + specDownloadTimeout = 60 * time.Second + localDelegateChainNamePrefix = "k8s_internal_local_delegation_chain_" + localDelegateChainNamePattern = localDelegateChainNamePrefix + "%010d" + + // A randomly generated UUID to differentiate local and remote eTags. + locallyGeneratedEtagPrefix = "\"6E8F849B434D4B98A569B9D7718876E9-" +) + +// IsLocalAPIService returns true for local specs from delegates. +func IsLocalAPIService(apiServiceName string) bool { + return strings.HasPrefix(apiServiceName, localDelegateChainNamePrefix) +} + +// GetAPIServiceNames returns the names of APIServices recorded in specAggregator.openAPISpecs. +// We use this function to pass the names of local APIServices to the controller in this package, +// so that the controller can periodically sync the OpenAPI spec from delegation API servers. +func (s *specAggregator) GetAPIServiceNames() []string { + names := make([]string, 0, len(s.openAPISpecs)) + for key := range s.openAPISpecs { + names = append(names, key) + } + return names +} + +// BuildAndRegisterAggregator registered OpenAPI aggregator handler. This function is not thread safe as it only being called on startup. +func BuildAndRegisterAggregator(downloader *Downloader, delegationTarget server.DelegationTarget, webServices []*restful.WebService, + config *common.Config, pathHandler common.PathHandler) (SpecAggregator, error) { + s := &specAggregator{ + openAPISpecs: map[string]*openAPISpecInfo{}, + } + + i := 0 + // Build Aggregator's spec + aggregatorOpenAPISpec, err := builder.BuildOpenAPISpec(webServices, config) + if err != nil { + return nil, err + } + + // Reserving non-name spec for aggregator's Spec. + s.addLocalSpec(aggregatorOpenAPISpec, nil, fmt.Sprintf(localDelegateChainNamePattern, i), "") + i++ + for delegate := delegationTarget; delegate != nil; delegate = delegate.NextDelegate() { + handler := delegate.UnprotectedHandler() + if handler == nil { + continue + } + delegateSpec, etag, _, err := downloader.Download(handler, "") + if err != nil { + // ignore errors for the empty delegate we attach at the end the chain + // atm the empty delegate returns 503 when the server hasn't been fully initialized + // and the spec downloader only silences 404s + if len(delegate.ListedPaths()) == 0 && delegate.NextDelegate() == nil { + continue + } + return nil, err + } + if delegateSpec == nil { + continue + } + s.addLocalSpec(delegateSpec, handler, fmt.Sprintf(localDelegateChainNamePattern, i), etag) + i++ + } + + // Build initial spec to serve. + klog.V(2).Infof("Building initial OpenAPI spec") + defer func(start time.Time) { + duration := time.Since(start) + klog.V(2).Infof("Finished initial OpenAPI spec generation after %v", duration) + + regenerationCounter.With(map[string]string{"apiservice": "*", "reason": "startup"}) + regenerationDurationGauge.With(map[string]string{"reason": "startup"}).Set(duration.Seconds()) + }(time.Now()) + specToServe, err := s.buildOpenAPISpec() + if err != nil { + return nil, err + } + + // Install handler + s.openAPIVersionedService, err = handler.NewOpenAPIService(specToServe) + if err != nil { + return nil, err + } + err = s.openAPIVersionedService.RegisterOpenAPIVersionedService("/openapi/v2", pathHandler) + if err != nil { + return nil, err + } + + return s, nil +} + +type specAggregator struct { + // mutex protects all members of this struct. + rwMutex sync.RWMutex + + // Map of API Services' OpenAPI specs by their name + openAPISpecs map[string]*openAPISpecInfo + + // provided for dynamic OpenAPI spec + openAPIVersionedService *handler.OpenAPIService +} + +var _ SpecAggregator = &specAggregator{} + +// This function is not thread safe as it only being called on startup. +func (s *specAggregator) addLocalSpec(spec *spec.Swagger, localHandler http.Handler, name, etag string) { + localAPIService := v1.APIService{} + localAPIService.Name = name + s.openAPISpecs[name] = &openAPISpecInfo{ + etag: etag, + apiService: localAPIService, + handler: localHandler, + spec: spec, + } +} + +// openAPISpecInfo is used to store OpenAPI spec with its priority. +// It can be used to sort specs with their priorities. +type openAPISpecInfo struct { + apiService v1.APIService + + // Specification of this API Service. If null then the spec is not loaded yet. + spec *spec.Swagger + handler http.Handler + etag string +} + +// buildOpenAPISpec aggregates all OpenAPI specs. It is not thread-safe. The caller is responsible to hold proper locks. +func (s *specAggregator) buildOpenAPISpec() (specToReturn *spec.Swagger, err error) { + specs := []openAPISpecInfo{} + for _, specInfo := range s.openAPISpecs { + if specInfo.spec == nil { + continue + } + // Copy the spec before removing the defaults. + localSpec := *specInfo.spec + localSpecInfo := *specInfo + localSpecInfo.spec = &localSpec + localSpecInfo.spec.Definitions = handler.PruneDefaults(specInfo.spec.Definitions) + specs = append(specs, localSpecInfo) + } + if len(specs) == 0 { + return &spec.Swagger{}, nil + } + sortByPriority(specs) + for _, specInfo := range specs { + if specToReturn == nil { + specToReturn = &spec.Swagger{} + *specToReturn = *specInfo.spec + // Paths and Definitions are set by MergeSpecsIgnorePathConflict + specToReturn.Paths = nil + specToReturn.Definitions = nil + } + if err := aggregator.MergeSpecsIgnorePathConflict(specToReturn, specInfo.spec); err != nil { + return nil, err + } + } + + return specToReturn, nil +} + +// updateOpenAPISpec aggregates all OpenAPI specs. It is not thread-safe. The caller is responsible to hold proper locks. +func (s *specAggregator) updateOpenAPISpec() error { + if s.openAPIVersionedService == nil { + return nil + } + specToServe, err := s.buildOpenAPISpec() + if err != nil { + return err + } + return s.openAPIVersionedService.UpdateSpec(specToServe) +} + +// tryUpdatingServiceSpecs tries updating openAPISpecs map with specified specInfo, and keeps the map intact +// if the update fails. +func (s *specAggregator) tryUpdatingServiceSpecs(specInfo *openAPISpecInfo) error { + if specInfo == nil { + return fmt.Errorf("invalid input: specInfo must be non-nil") + } + _, updated := s.openAPISpecs[specInfo.apiService.Name] + origSpecInfo, existedBefore := s.openAPISpecs[specInfo.apiService.Name] + s.openAPISpecs[specInfo.apiService.Name] = specInfo + + // Skip aggregation if OpenAPI spec didn't change + if existedBefore && origSpecInfo != nil && origSpecInfo.etag == specInfo.etag { + return nil + } + klog.V(2).Infof("Updating OpenAPI spec because %s is updated", specInfo.apiService.Name) + defer func(start time.Time) { + duration := time.Since(start) + klog.V(2).Infof("Finished OpenAPI spec generation after %v", duration) + + reason := "add" + if updated { + reason = "update" + } + + regenerationCounter.With(map[string]string{"apiservice": specInfo.apiService.Name, "reason": reason}) + regenerationDurationGauge.With(map[string]string{"reason": reason}).Set(duration.Seconds()) + }(time.Now()) + if err := s.updateOpenAPISpec(); err != nil { + if existedBefore { + s.openAPISpecs[specInfo.apiService.Name] = origSpecInfo + } else { + delete(s.openAPISpecs, specInfo.apiService.Name) + } + return err + } + return nil +} + +// tryDeleteServiceSpecs tries delete specified specInfo from openAPISpecs map, and keeps the map intact +// if the update fails. +func (s *specAggregator) tryDeleteServiceSpecs(apiServiceName string) error { + orgSpecInfo, exists := s.openAPISpecs[apiServiceName] + if !exists { + return nil + } + delete(s.openAPISpecs, apiServiceName) + klog.V(2).Infof("Updating OpenAPI spec because %s is removed", apiServiceName) + defer func(start time.Time) { + duration := time.Since(start) + klog.V(2).Infof("Finished OpenAPI spec generation after %v", duration) + + regenerationCounter.With(map[string]string{"apiservice": apiServiceName, "reason": "delete"}) + regenerationDurationGauge.With(map[string]string{"reason": "delete"}).Set(duration.Seconds()) + }(time.Now()) + if err := s.updateOpenAPISpec(); err != nil { + s.openAPISpecs[apiServiceName] = orgSpecInfo + return err + } + return nil +} + +// UpdateAPIServiceSpec updates the api service's OpenAPI spec. It is thread safe. +func (s *specAggregator) UpdateAPIServiceSpec(apiServiceName string, spec *spec.Swagger, etag string) error { + s.rwMutex.Lock() + defer s.rwMutex.Unlock() + + specInfo, existingService := s.openAPISpecs[apiServiceName] + if !existingService { + return fmt.Errorf("APIService %q does not exists", apiServiceName) + } + + // For APIServices (non-local) specs, only merge their /apis/ prefixed endpoint as it is the only paths + // proxy handler delegates. + if specInfo.apiService.Spec.Service != nil { + spec = aggregator.FilterSpecByPathsWithoutSideEffects(spec, []string{"/apis/"}) + } + + return s.tryUpdatingServiceSpecs(&openAPISpecInfo{ + apiService: specInfo.apiService, + spec: spec, + handler: specInfo.handler, + etag: etag, + }) +} + +// AddUpdateAPIService adds or updates the api service. It is thread safe. +func (s *specAggregator) AddUpdateAPIService(handler http.Handler, apiService *v1.APIService) error { + s.rwMutex.Lock() + defer s.rwMutex.Unlock() + + if apiService.Spec.Service == nil { + // All local specs should be already aggregated using local delegate chain + return nil + } + + newSpec := &openAPISpecInfo{ + apiService: *apiService, + handler: handler, + } + if specInfo, existingService := s.openAPISpecs[apiService.Name]; existingService { + newSpec.etag = specInfo.etag + newSpec.spec = specInfo.spec + } + return s.tryUpdatingServiceSpecs(newSpec) +} + +// RemoveAPIServiceSpec removes an api service from OpenAPI aggregation. If it does not exist, no error is returned. +// It is thread safe. +func (s *specAggregator) RemoveAPIServiceSpec(apiServiceName string) error { + s.rwMutex.Lock() + defer s.rwMutex.Unlock() + + if _, existingService := s.openAPISpecs[apiServiceName]; !existingService { + return nil + } + + return s.tryDeleteServiceSpecs(apiServiceName) +} + +// GetAPIServiceSpec returns api service spec info +func (s *specAggregator) GetAPIServiceInfo(apiServiceName string) (handler http.Handler, etag string, exists bool) { + s.rwMutex.RLock() + defer s.rwMutex.RUnlock() + + if info, existingService := s.openAPISpecs[apiServiceName]; existingService { + return info.handler, info.etag, true + } + return nil, "", false +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/downloader.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/downloader.go new file mode 100644 index 000000000000..a6c50907e828 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/downloader.go @@ -0,0 +1,142 @@ +/* +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 aggregator + +import ( + "crypto/sha512" + "fmt" + "net/http" + "strings" + + utiljson "k8s.io/apimachinery/pkg/util/json" + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kube-openapi/pkg/validation/spec" +) + +// Downloader is the OpenAPI downloader type. It will try to download spec from /openapi/v2 or /swagger.json endpoint. +type Downloader struct { +} + +// NewDownloader creates a new OpenAPI Downloader. +func NewDownloader() Downloader { + return Downloader{} +} + +func (s *Downloader) handlerWithUser(handler http.Handler, info user.Info) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + req = req.WithContext(request.WithUser(req.Context(), info)) + handler.ServeHTTP(w, req) + }) +} + +func etagFor(data []byte) string { + return fmt.Sprintf("%s%X\"", locallyGeneratedEtagPrefix, sha512.Sum512(data)) +} + +// Download downloads openAPI spec from /openapi/v2 endpoint of the given handler. +// httpStatus is only valid if err == nil +func (s *Downloader) Download(handler http.Handler, etag string) (returnSpec *spec.Swagger, newEtag string, httpStatus int, err error) { + handler = s.handlerWithUser(handler, &user.DefaultInfo{Name: aggregatorUser}) + handler = http.TimeoutHandler(handler, specDownloadTimeout, "request timed out") + + req, err := http.NewRequest("GET", "/openapi/v2", nil) + if err != nil { + return nil, "", 0, err + } + req.Header.Add("Accept", "application/json") + + // Only pass eTag if it is not generated locally + if len(etag) > 0 && !strings.HasPrefix(etag, locallyGeneratedEtagPrefix) { + req.Header.Add("If-None-Match", etag) + } + + writer := newInMemoryResponseWriter() + handler.ServeHTTP(writer, req) + + switch writer.respCode { + case http.StatusNotModified: + if len(etag) == 0 { + return nil, etag, http.StatusNotModified, fmt.Errorf("http.StatusNotModified is not allowed in absence of etag") + } + return nil, etag, http.StatusNotModified, nil + case http.StatusNotFound: + // Gracefully skip 404, assuming the server won't provide any spec + return nil, "", http.StatusNotFound, nil + case http.StatusOK: + openAPISpec := &spec.Swagger{} + if err := utiljson.Unmarshal(writer.data, openAPISpec); err != nil { + return nil, "", 0, err + } + newEtag = writer.Header().Get("Etag") + if len(newEtag) == 0 { + newEtag = etagFor(writer.data) + if len(etag) > 0 && strings.HasPrefix(etag, locallyGeneratedEtagPrefix) { + // The function call with an etag and server does not report an etag. + // That means this server does not support etag and the etag that passed + // to the function generated previously by us. Just compare etags and + // return StatusNotModified if they are the same. + if etag == newEtag { + return nil, etag, http.StatusNotModified, nil + } + } + } + return openAPISpec, newEtag, http.StatusOK, nil + default: + return nil, "", 0, fmt.Errorf("failed to retrieve openAPI spec, http error: %s", writer.String()) + } +} + +// inMemoryResponseWriter is a http.Writer that keep the response in memory. +type inMemoryResponseWriter struct { + writeHeaderCalled bool + header http.Header + respCode int + data []byte +} + +func newInMemoryResponseWriter() *inMemoryResponseWriter { + return &inMemoryResponseWriter{header: http.Header{}} +} + +func (r *inMemoryResponseWriter) Header() http.Header { + return r.header +} + +func (r *inMemoryResponseWriter) WriteHeader(code int) { + r.writeHeaderCalled = true + r.respCode = code +} + +func (r *inMemoryResponseWriter) Write(in []byte) (int, error) { + if !r.writeHeaderCalled { + r.WriteHeader(http.StatusOK) + } + r.data = append(r.data, in...) + return len(in), nil +} + +func (r *inMemoryResponseWriter) String() string { + s := fmt.Sprintf("ResponseCode: %d", r.respCode) + if r.data != nil { + s += fmt.Sprintf(", Body: %s", string(r.data)) + } + if r.header != nil { + s += fmt.Sprintf(", Header: %s", r.header) + } + return s +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/metrics.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/metrics.go new file mode 100644 index 000000000000..b0a6e54e43ce --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/metrics.go @@ -0,0 +1,46 @@ +/* +Copyright 2019 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 aggregator + +import ( + "k8s.io/component-base/metrics" + "k8s.io/component-base/metrics/legacyregistry" +) + +var ( + regenerationCounter = metrics.NewCounterVec( + &metrics.CounterOpts{ + Name: "aggregator_openapi_v2_regeneration_count", + Help: "Counter of OpenAPI v2 spec regeneration count broken down by causing APIService name and reason.", + StabilityLevel: metrics.ALPHA, + }, + []string{"apiservice", "reason"}, + ) + regenerationDurationGauge = metrics.NewGaugeVec( + &metrics.GaugeOpts{ + Name: "aggregator_openapi_v2_regeneration_duration", + Help: "Gauge of OpenAPI v2 spec regeneration duration in seconds.", + StabilityLevel: metrics.ALPHA, + }, + []string{"reason"}, + ) +) + +func init() { + legacyregistry.MustRegister(regenerationCounter) + legacyregistry.MustRegister(regenerationDurationGauge) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/priority.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/priority.go new file mode 100644 index 000000000000..9847de3c6c62 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator/priority.go @@ -0,0 +1,74 @@ +/* +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 aggregator + +import ( + "sort" +) + +// byPriority can be used in sort.Sort to sort specs with their priorities. +type byPriority struct { + specs []openAPISpecInfo + groupPriorities map[string]int32 +} + +func (a byPriority) Len() int { return len(a.specs) } +func (a byPriority) Swap(i, j int) { a.specs[i], a.specs[j] = a.specs[j], a.specs[i] } +func (a byPriority) Less(i, j int) bool { + // All local specs will come first + if a.specs[i].apiService.Spec.Service == nil && a.specs[j].apiService.Spec.Service != nil { + return true + } + if a.specs[i].apiService.Spec.Service != nil && a.specs[j].apiService.Spec.Service == nil { + return false + } + // WARNING: This will result in not following priorities for local APIServices. + if a.specs[i].apiService.Spec.Service == nil { + // Sort local specs with their name. This is the order in the delegation chain (aggregator first). + return a.specs[i].apiService.Name < a.specs[j].apiService.Name + } + var iPriority, jPriority int32 + if a.specs[i].apiService.Spec.Group == a.specs[j].apiService.Spec.Group { + iPriority = a.specs[i].apiService.Spec.VersionPriority + jPriority = a.specs[i].apiService.Spec.VersionPriority + } else { + iPriority = a.groupPriorities[a.specs[i].apiService.Spec.Group] + jPriority = a.groupPriorities[a.specs[j].apiService.Spec.Group] + } + if iPriority != jPriority { + // Sort by priority, higher first + return iPriority > jPriority + } + // Sort by service name. + return a.specs[i].apiService.Name < a.specs[j].apiService.Name +} + +func sortByPriority(specs []openAPISpecInfo) { + b := byPriority{ + specs: specs, + groupPriorities: map[string]int32{}, + } + for _, spec := range specs { + if spec.apiService.Spec.Service == nil { + continue + } + if pr, found := b.groupPriorities[spec.apiService.Spec.Group]; !found || spec.apiService.Spec.GroupPriorityMinimum > pr { + b.groupPriorities[spec.apiService.Spec.Group] = spec.apiService.Spec.GroupPriorityMinimum + } + } + sort.Sort(b) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/controller.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/controller.go new file mode 100644 index 000000000000..98e6ecc6dad6 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapi/controller.go @@ -0,0 +1,196 @@ +/* +Copyright 2016 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 openapi + +import ( + "fmt" + "net/http" + "time" + + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator" +) + +const ( + successfulUpdateDelay = time.Minute + successfulUpdateDelayLocal = time.Second + failedUpdateMaxExpDelay = time.Hour +) + +type syncAction int + +const ( + syncRequeue syncAction = iota + syncRequeueRateLimited + syncNothing +) + +// AggregationController periodically check for changes in OpenAPI specs of APIServices and update/remove +// them if necessary. +type AggregationController struct { + openAPIAggregationManager aggregator.SpecAggregator + queue workqueue.RateLimitingInterface + downloader *aggregator.Downloader + + // To allow injection for testing. + syncHandler func(key string) (syncAction, error) +} + +// NewAggregationController creates new OpenAPI aggregation controller. +func NewAggregationController(downloader *aggregator.Downloader, openAPIAggregationManager aggregator.SpecAggregator) *AggregationController { + c := &AggregationController{ + openAPIAggregationManager: openAPIAggregationManager, + queue: workqueue.NewNamedRateLimitingQueue( + workqueue.NewItemExponentialFailureRateLimiter(successfulUpdateDelay, failedUpdateMaxExpDelay), + "open_api_aggregation_controller", + ), + downloader: downloader, + } + + c.syncHandler = c.sync + + // update each service at least once, also those which are not coming from APIServices, namely local services + for _, name := range openAPIAggregationManager.GetAPIServiceNames() { + c.queue.AddAfter(name, time.Second) + } + + return c +} + +// Run starts OpenAPI AggregationController +func (c *AggregationController) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + klog.Info("Starting OpenAPI AggregationController") + defer klog.Info("Shutting down OpenAPI AggregationController") + + go wait.Until(c.runWorker, time.Second, stopCh) + + <-stopCh +} + +func (c *AggregationController) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. +func (c *AggregationController) processNextWorkItem() bool { + key, quit := c.queue.Get() + defer c.queue.Done(key) + if quit { + return false + } + + if aggregator.IsLocalAPIService(key.(string)) { + // for local delegation targets that are aggregated once per second, log at + // higher level to avoid flooding the log + klog.V(6).Infof("OpenAPI AggregationController: Processing item %s", key) + } else { + klog.V(4).Infof("OpenAPI AggregationController: Processing item %s", key) + } + + action, err := c.syncHandler(key.(string)) + if err == nil { + c.queue.Forget(key) + } else { + utilruntime.HandleError(fmt.Errorf("loading OpenAPI spec for %q failed with: %v", key, err)) + } + + switch action { + case syncRequeue: + if aggregator.IsLocalAPIService(key.(string)) { + klog.V(7).Infof("OpenAPI AggregationController: action for local item %s: Requeue after %s.", key, successfulUpdateDelayLocal) + c.queue.AddAfter(key, successfulUpdateDelayLocal) + } else { + klog.V(7).Infof("OpenAPI AggregationController: action for item %s: Requeue.", key) + c.queue.AddAfter(key, successfulUpdateDelay) + } + case syncRequeueRateLimited: + klog.Infof("OpenAPI AggregationController: action for item %s: Rate Limited Requeue.", key) + c.queue.AddRateLimited(key) + case syncNothing: + klog.Infof("OpenAPI AggregationController: action for item %s: Nothing (removed from the queue).", key) + } + + return true +} + +func (c *AggregationController) sync(key string) (syncAction, error) { + handler, etag, exists := c.openAPIAggregationManager.GetAPIServiceInfo(key) + if !exists || handler == nil { + return syncNothing, nil + } + returnSpec, newEtag, httpStatus, err := c.downloader.Download(handler, etag) + switch { + case err != nil: + return syncRequeueRateLimited, err + case httpStatus == http.StatusNotModified: + case httpStatus == http.StatusNotFound || returnSpec == nil: + return syncRequeueRateLimited, fmt.Errorf("OpenAPI spec does not exist") + case httpStatus == http.StatusOK: + if err := c.openAPIAggregationManager.UpdateAPIServiceSpec(key, returnSpec, newEtag); err != nil { + return syncRequeueRateLimited, err + } + } + return syncRequeue, nil +} + +// AddAPIService adds a new API Service to OpenAPI Aggregation. +func (c *AggregationController) AddAPIService(handler http.Handler, apiService *v1.APIService) { + if apiService.Spec.Service == nil { + return + } + if err := c.openAPIAggregationManager.AddUpdateAPIService(handler, apiService); err != nil { + utilruntime.HandleError(fmt.Errorf("adding %q to AggregationController failed with: %v", apiService.Name, err)) + } + c.queue.AddAfter(apiService.Name, time.Second) +} + +// UpdateAPIService updates API Service's info and handler. +func (c *AggregationController) UpdateAPIService(handler http.Handler, apiService *v1.APIService) { + if apiService.Spec.Service == nil { + return + } + if err := c.openAPIAggregationManager.AddUpdateAPIService(handler, apiService); err != nil { + utilruntime.HandleError(fmt.Errorf("updating %q to AggregationController failed with: %v", apiService.Name, err)) + } + key := apiService.Name + if c.queue.NumRequeues(key) > 0 { + // The item has failed before. Remove it from failure queue and + // update it in a second + c.queue.Forget(key) + c.queue.AddAfter(key, time.Second) + } + // Else: The item has been succeeded before and it will be updated soon (after successfulUpdateDelay) + // we don't add it again as it will cause a duplication of items. +} + +// RemoveAPIService removes API Service from OpenAPI Aggregation Controller. +func (c *AggregationController) RemoveAPIService(apiServiceName string) { + if err := c.openAPIAggregationManager.RemoveAPIServiceSpec(apiServiceName); err != nil { + utilruntime.HandleError(fmt.Errorf("removing %q from AggregationController failed with: %v", apiServiceName, err)) + } + // This will only remove it if it was failing before. If it was successful, processNextWorkItem will figure it out + // and will not add it again to the queue. + c.queue.Forget(apiServiceName) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator/aggregator.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator/aggregator.go new file mode 100644 index 000000000000..67e793077e9b --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator/aggregator.go @@ -0,0 +1,276 @@ +/* +Copyright 2021 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 aggregator + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "strings" + "sync" + "time" + + "k8s.io/apiserver/pkg/server" + "k8s.io/apiserver/pkg/server/mux" + "k8s.io/klog/v2" + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/handler3" + "k8s.io/kube-openapi/pkg/openapiconv" + + v2aggregator "k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator" +) + +// SpecProxier proxies OpenAPI V3 requests to their respective APIService +type SpecProxier interface { + AddUpdateAPIService(handler http.Handler, apiService *v1.APIService) + UpdateAPIServiceSpec(apiServiceName string) error + RemoveAPIServiceSpec(apiServiceName string) + GetAPIServiceNames() []string +} + +const ( + aggregatorUser = "system:aggregator" + specDownloadTimeout = 60 * time.Second + localDelegateChainNamePrefix = "k8s_internal_local_delegation_chain_" + localDelegateChainNamePattern = localDelegateChainNamePrefix + "%010d" + openAPIV2Converter = "openapiv2converter" +) + +// IsLocalAPIService returns true for local specs from delegates. +func IsLocalAPIService(apiServiceName string) bool { + return strings.HasPrefix(apiServiceName, localDelegateChainNamePrefix) +} + +// GetAPIServiceNames returns the names of APIServices recorded in apiServiceInfo. +// We use this function to pass the names of local APIServices to the controller in this package, +// so that the controller can periodically sync the OpenAPI spec from delegation API servers. +func (s *specProxier) GetAPIServiceNames() []string { + s.rwMutex.RLock() + defer s.rwMutex.RUnlock() + + names := make([]string, 0, len(s.apiServiceInfo)) + for key := range s.apiServiceInfo { + names = append(names, key) + } + return names +} + +// BuildAndRegisterAggregator registered OpenAPI aggregator handler. This function is not thread safe as it only being called on startup. +func BuildAndRegisterAggregator(downloader Downloader, delegationTarget server.DelegationTarget, pathHandler common.PathHandlerByGroupVersion) (SpecProxier, error) { + s := &specProxier{ + apiServiceInfo: map[string]*openAPIV3APIServiceInfo{}, + downloader: downloader, + } + + i := 1 + for delegate := delegationTarget; delegate != nil; delegate = delegate.NextDelegate() { + handler := delegate.UnprotectedHandler() + if handler == nil { + continue + } + + apiServiceName := fmt.Sprintf(localDelegateChainNamePattern, i) + localAPIService := v1.APIService{} + localAPIService.Name = apiServiceName + s.AddUpdateAPIService(handler, &localAPIService) + s.UpdateAPIServiceSpec(apiServiceName) + i++ + } + + handler, err := handler3.NewOpenAPIService(nil) + if err != nil { + return s, err + } + s.openAPIV2ConverterHandler = handler + openAPIV2ConverterMux := mux.NewPathRecorderMux(openAPIV2Converter) + s.openAPIV2ConverterHandler.RegisterOpenAPIV3VersionedService("/openapi/v3", openAPIV2ConverterMux) + openAPIV2ConverterAPIService := v1.APIService{} + openAPIV2ConverterAPIService.Name = openAPIV2Converter + s.AddUpdateAPIService(openAPIV2ConverterMux, &openAPIV2ConverterAPIService) + s.register(pathHandler) + + return s, nil +} + +// AddUpdateAPIService adds or updates the api service. It is thread safe. +func (s *specProxier) AddUpdateAPIService(handler http.Handler, apiservice *v1.APIService) { + s.rwMutex.Lock() + defer s.rwMutex.Unlock() + // If the APIService is being updated, use the existing struct. + if apiServiceInfo, ok := s.apiServiceInfo[apiservice.Name]; ok { + apiServiceInfo.apiService = *apiservice + apiServiceInfo.handler = handler + } + s.apiServiceInfo[apiservice.Name] = &openAPIV3APIServiceInfo{ + apiService: *apiservice, + handler: handler, + } +} + +func getGroupVersionStringFromAPIService(apiService v1.APIService) string { + if apiService.Spec.Group == "" && apiService.Spec.Version == "" { + return "" + } + return "apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version +} + +// UpdateAPIServiceSpec updates all the OpenAPI v3 specs that the APIService serves. +// It is thread safe. +func (s *specProxier) UpdateAPIServiceSpec(apiServiceName string) error { + s.rwMutex.Lock() + defer s.rwMutex.Unlock() + return s.updateAPIServiceSpecLocked(apiServiceName) +} + +func (s *specProxier) updateAPIServiceSpecLocked(apiServiceName string) error { + apiService, exists := s.apiServiceInfo[apiServiceName] + if !exists { + return fmt.Errorf("APIService %s does not exist for update", apiServiceName) + } + + if !apiService.isLegacyAPIService { + gv, httpStatus, err := s.downloader.OpenAPIV3Root(apiService.handler) + if err != nil { + return err + } + if httpStatus == http.StatusNotFound { + apiService.isLegacyAPIService = true + } else { + s.apiServiceInfo[apiServiceName].discovery = gv + return nil + } + } + + newDownloader := v2aggregator.Downloader{} + v2Spec, etag, httpStatus, err := newDownloader.Download(apiService.handler, apiService.etag) + if err != nil { + return err + } + apiService.etag = etag + if httpStatus == http.StatusOK { + v3Spec := openapiconv.ConvertV2ToV3(v2Spec) + s.openAPIV2ConverterHandler.UpdateGroupVersion(getGroupVersionStringFromAPIService(apiService.apiService), v3Spec) + s.updateAPIServiceSpecLocked(openAPIV2Converter) + } + return nil +} + +type specProxier struct { + // mutex protects all members of this struct. + rwMutex sync.RWMutex + + // OpenAPI V3 specs by APIService name + apiServiceInfo map[string]*openAPIV3APIServiceInfo + + // For downloading the OpenAPI v3 specs from apiservices + downloader Downloader + + openAPIV2ConverterHandler *handler3.OpenAPIService +} + +var _ SpecProxier = &specProxier{} + +type openAPIV3APIServiceInfo struct { + apiService v1.APIService + handler http.Handler + discovery *handler3.OpenAPIV3Discovery + + // These fields are only used if the /openapi/v3 endpoint is not served by an APIService + // Legacy APIService indicates that an APIService does not support OpenAPI V3, and the OpenAPI V2 + // will be downloaded, converted to V3 (lossy), and served by the aggregator + etag string + isLegacyAPIService bool +} + +// RemoveAPIServiceSpec removes an api service from the OpenAPI map. If it does not exist, no error is returned. +// It is thread safe. +func (s *specProxier) RemoveAPIServiceSpec(apiServiceName string) { + s.rwMutex.Lock() + defer s.rwMutex.Unlock() + if apiServiceInfo, ok := s.apiServiceInfo[apiServiceName]; ok { + s.openAPIV2ConverterHandler.DeleteGroupVersion(getGroupVersionStringFromAPIService(apiServiceInfo.apiService)) + delete(s.apiServiceInfo, apiServiceName) + } +} + +func (s *specProxier) getOpenAPIV3Root() handler3.OpenAPIV3Discovery { + s.rwMutex.RLock() + defer s.rwMutex.RUnlock() + + merged := handler3.OpenAPIV3Discovery{ + Paths: make(map[string]handler3.OpenAPIV3DiscoveryGroupVersion), + } + + for _, apiServiceInfo := range s.apiServiceInfo { + if apiServiceInfo.discovery == nil { + continue + } + + for key, item := range apiServiceInfo.discovery.Paths { + merged.Paths[key] = item + } + } + return merged +} + +// handleDiscovery is the handler for OpenAPI V3 Discovery +func (s *specProxier) handleDiscovery(w http.ResponseWriter, r *http.Request) { + merged := s.getOpenAPIV3Root() + j, err := json.Marshal(&merged) + if err != nil { + w.WriteHeader(500) + klog.Errorf("failed to created merged OpenAPIv3 discovery response: %s", err.Error()) + return + } + + http.ServeContent(w, r, "/openapi/v3", time.Now(), bytes.NewReader(j)) +} + +// handleGroupVersion is the OpenAPI V3 handler for a specified group/version +func (s *specProxier) handleGroupVersion(w http.ResponseWriter, r *http.Request) { + s.rwMutex.RLock() + defer s.rwMutex.RUnlock() + + // TODO: Import this logic from kube-openapi instead of duplicating + // URLs for OpenAPI V3 have the format /openapi/v3/ + // SplitAfterN with 4 yields ["", "openapi", "v3", ] + url := strings.SplitAfterN(r.URL.Path, "/", 4) + targetGV := url[3] + + for _, apiServiceInfo := range s.apiServiceInfo { + if apiServiceInfo.discovery == nil { + continue + } + + for key := range apiServiceInfo.discovery.Paths { + if targetGV == key { + apiServiceInfo.handler.ServeHTTP(w, r) + return + } + } + } + // No group-versions match the desired request + w.WriteHeader(404) +} + +// Register registers the OpenAPI V3 Discovery and GroupVersion handlers +func (s *specProxier) register(handler common.PathHandlerByGroupVersion) { + handler.Handle("/openapi/v3", http.HandlerFunc(s.handleDiscovery)) + handler.HandlePrefix("/openapi/v3/", http.HandlerFunc(s.handleGroupVersion)) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator/downloader.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator/downloader.go new file mode 100644 index 000000000000..45b2467980ad --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator/downloader.go @@ -0,0 +1,115 @@ +/* +Copyright 2021 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 aggregator + +import ( + "encoding/json" + "fmt" + "net/http" + + "k8s.io/apiserver/pkg/authentication/user" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/kube-openapi/pkg/handler3" +) + +type NotFoundError struct { +} + +func (e *NotFoundError) Error() string { + return "" +} + +// Downloader is the OpenAPI downloader type. It will try to download spec from /openapi/v3 and /openap/v3// endpoints. +type Downloader struct { +} + +// NewDownloader creates a new OpenAPI Downloader. +func NewDownloader() Downloader { + return Downloader{} +} + +func (s *Downloader) handlerWithUser(handler http.Handler, info user.Info) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + req = req.WithContext(request.WithUser(req.Context(), info)) + handler.ServeHTTP(w, req) + }) +} + +// OpenAPIV3Root downloads the OpenAPI V3 root document from an APIService +func (s *Downloader) OpenAPIV3Root(handler http.Handler) (*handler3.OpenAPIV3Discovery, int, error) { + handler = s.handlerWithUser(handler, &user.DefaultInfo{Name: aggregatorUser}) + handler = http.TimeoutHandler(handler, specDownloadTimeout, "request timed out") + + req, err := http.NewRequest("GET", "/openapi/v3", nil) + if err != nil { + return nil, 0, err + } + writer := newInMemoryResponseWriter() + handler.ServeHTTP(writer, req) + + switch writer.respCode { + case http.StatusNotFound: + return nil, writer.respCode, nil + case http.StatusOK: + groups := handler3.OpenAPIV3Discovery{} + if err := json.Unmarshal(writer.data, &groups); err != nil { + return nil, writer.respCode, err + } + return &groups, writer.respCode, nil + } + return nil, writer.respCode, fmt.Errorf("Error, could not get list of group versions for APIService") +} + +// inMemoryResponseWriter is a http.Writer that keep the response in memory. +type inMemoryResponseWriter struct { + writeHeaderCalled bool + header http.Header + respCode int + data []byte +} + +func newInMemoryResponseWriter() *inMemoryResponseWriter { + return &inMemoryResponseWriter{header: http.Header{}} +} + +func (r *inMemoryResponseWriter) Header() http.Header { + return r.header +} + +func (r *inMemoryResponseWriter) WriteHeader(code int) { + r.writeHeaderCalled = true + r.respCode = code +} + +func (r *inMemoryResponseWriter) Write(in []byte) (int, error) { + if !r.writeHeaderCalled { + r.WriteHeader(http.StatusOK) + } + r.data = append(r.data, in...) + return len(in), nil +} + +func (r *inMemoryResponseWriter) String() string { + s := fmt.Sprintf("ResponseCode: %d", r.respCode) + if r.data != nil { + s += fmt.Sprintf(", Body: %s", string(r.data)) + } + if r.header != nil { + s += fmt.Sprintf(", Header: %s", r.header) + } + return s +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/controller.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/controller.go new file mode 100644 index 000000000000..ed69afe40c10 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/openapiv3/controller.go @@ -0,0 +1,174 @@ +/* +Copyright 2021 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 openapiv3 + +import ( + "fmt" + "net/http" + "time" + + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/client-go/util/workqueue" + "k8s.io/klog/v2" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + "k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator" +) + +const ( + successfulUpdateDelay = time.Minute + successfulUpdateDelayLocal = time.Second + failedUpdateMaxExpDelay = time.Hour +) + +type syncAction int + +const ( + syncRequeue syncAction = iota + syncRequeueRateLimited + syncNothing +) + +// AggregationController periodically checks the list of group-versions handled by each APIService and updates the discovery page periodically +type AggregationController struct { + openAPIAggregationManager aggregator.SpecProxier + queue workqueue.RateLimitingInterface + + // To allow injection for testing. + syncHandler func(key string) (syncAction, error) +} + +// NewAggregationController creates new OpenAPI aggregation controller. +func NewAggregationController(openAPIAggregationManager aggregator.SpecProxier) *AggregationController { + c := &AggregationController{ + openAPIAggregationManager: openAPIAggregationManager, + queue: workqueue.NewNamedRateLimitingQueue( + workqueue.NewItemExponentialFailureRateLimiter(successfulUpdateDelay, failedUpdateMaxExpDelay), + "open_api_v3_aggregation_controller", + ), + } + + c.syncHandler = c.sync + + // update each service at least once, also those which are not coming from APIServices, namely local services + for _, name := range openAPIAggregationManager.GetAPIServiceNames() { + c.queue.AddAfter(name, time.Second) + } + + return c +} + +// Run starts OpenAPI AggregationController +func (c *AggregationController) Run(stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + klog.Info("Starting OpenAPI V3 AggregationController") + defer klog.Info("Shutting down OpenAPI V3 AggregationController") + + go wait.Until(c.runWorker, time.Second, stopCh) + + <-stopCh +} + +func (c *AggregationController) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. +func (c *AggregationController) processNextWorkItem() bool { + key, quit := c.queue.Get() + defer c.queue.Done(key) + if quit { + return false + } + + if aggregator.IsLocalAPIService(key.(string)) { + // for local delegation targets that are aggregated once per second, log at + // higher level to avoid flooding the log + klog.V(6).Infof("OpenAPI AggregationController: Processing item %s", key) + } else { + klog.V(4).Infof("OpenAPI AggregationController: Processing item %s", key) + } + + action, err := c.syncHandler(key.(string)) + if err == nil { + c.queue.Forget(key) + } else { + utilruntime.HandleError(fmt.Errorf("loading OpenAPI spec for %q failed with: %v", key, err)) + } + + switch action { + case syncRequeue: + if aggregator.IsLocalAPIService(key.(string)) { + klog.V(7).Infof("OpenAPI AggregationController: action for local item %s: Requeue after %s.", key, successfulUpdateDelayLocal) + c.queue.AddAfter(key, successfulUpdateDelayLocal) + } else { + klog.V(7).Infof("OpenAPI AggregationController: action for item %s: Requeue.", key) + c.queue.AddAfter(key, successfulUpdateDelay) + } + case syncRequeueRateLimited: + klog.Infof("OpenAPI AggregationController: action for item %s: Rate Limited Requeue.", key) + c.queue.AddRateLimited(key) + case syncNothing: + klog.Infof("OpenAPI AggregationController: action for item %s: Nothing (removed from the queue).", key) + } + + return true +} + +func (c *AggregationController) sync(key string) (syncAction, error) { + err := c.openAPIAggregationManager.UpdateAPIServiceSpec(key) + switch { + case err != nil: + return syncRequeueRateLimited, err + } + return syncRequeue, nil +} + +// AddAPIService adds a new API Service to OpenAPI Aggregation. +func (c *AggregationController) AddAPIService(handler http.Handler, apiService *v1.APIService) { + if apiService.Spec.Service == nil { + return + } + c.openAPIAggregationManager.AddUpdateAPIService(handler, apiService) + c.queue.AddAfter(apiService.Name, time.Second) +} + +// UpdateAPIService updates API Service's info and handler. +func (c *AggregationController) UpdateAPIService(handler http.Handler, apiService *v1.APIService) { + if apiService.Spec.Service == nil { + return + } + c.openAPIAggregationManager.AddUpdateAPIService(handler, apiService) + key := apiService.Name + if c.queue.NumRequeues(key) > 0 { + // The item has failed before. Remove it from failure queue and + // update it in a second + c.queue.Forget(key) + c.queue.AddAfter(key, time.Second) + } +} + +// RemoveAPIService removes API Service from OpenAPI Aggregation Controller. +func (c *AggregationController) RemoveAPIService(apiServiceName string) { + c.openAPIAggregationManager.RemoveAPIServiceSpec(apiServiceName) + // This will only remove it if it was failing before. If it was successful, processNextWorkItem will figure it out + // and will not add it again to the queue. + c.queue.Forget(apiServiceName) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/status/available_controller.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/status/available_controller.go new file mode 100644 index 000000000000..9c89b313167f --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/status/available_controller.go @@ -0,0 +1,689 @@ +/* +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 ( + "context" + "fmt" + "net" + "net/http" + "net/url" + "reflect" + "sync" + "time" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + utilnet "k8s.io/apimachinery/pkg/util/net" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apimachinery/pkg/util/wait" + "k8s.io/apiserver/pkg/server/egressselector" + v1informers "k8s.io/client-go/informers/core/v1" + v1listers "k8s.io/client-go/listers/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/cache" + "k8s.io/client-go/transport" + "k8s.io/client-go/util/workqueue" + "k8s.io/component-base/metrics/legacyregistry" + "k8s.io/klog/v2" + apiregistrationv1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + apiregistrationv1apihelper "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper" + apiregistrationclient "k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1" + informers "k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1" + listers "k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1" + "k8s.io/kube-aggregator/pkg/controllers" +) + +// making sure we only register metrics once into legacy registry +var registerIntoLegacyRegistryOnce sync.Once + +type certKeyFunc func() ([]byte, []byte) + +// ServiceResolver knows how to convert a service reference into an actual location. +type ServiceResolver interface { + ResolveEndpoint(namespace, name string, port int32) (*url.URL, error) +} + +// AvailableConditionController handles checking the availability of registered API services. +type AvailableConditionController struct { + apiServiceClient apiregistrationclient.APIServicesGetter + + apiServiceLister listers.APIServiceLister + apiServiceSynced cache.InformerSynced + + // serviceLister is used to get the IP to create the transport for + serviceLister v1listers.ServiceLister + servicesSynced cache.InformerSynced + + endpointsLister v1listers.EndpointsLister + endpointsSynced cache.InformerSynced + + // dialContext specifies the dial function for creating unencrypted TCP connections. + dialContext func(ctx context.Context, network, address string) (net.Conn, error) + proxyCurrentCertKeyContent certKeyFunc + serviceResolver ServiceResolver + + // To allow injection for testing. + syncFn func(key string) error + + queue workqueue.RateLimitingInterface + // map from service-namespace -> service-name -> apiservice names + cache map[string]map[string][]string + // this lock protects operations on the above cache + cacheLock sync.RWMutex + + // TLS config with customized dialer cannot be cached by the client-go + // tlsTransportCache. Use a local cache here to reduce the chance of + // the controller spamming idle connections with short-lived transports. + // NOTE: the cache works because we assume that the transports constructed + // by the controller only vary on the dynamic cert/key. + tlsCache *tlsTransportCache + + // metrics registered into legacy registry + metrics *availabilityMetrics +} + +type tlsTransportCache struct { + mu sync.Mutex + transports map[tlsCacheKey]http.RoundTripper +} + +func (c *tlsTransportCache) get(config *rest.Config) (http.RoundTripper, error) { + // If the available controller doesn't customzie the dialer (and we know from + // the code that the controller doesn't customzie other functions i.e. Proxy + // and GetCert (ExecProvider)), the config is cacheable by the client-go TLS + // transport cache. Let's skip the local cache and depend on the client-go cache. + if config.Dial == nil { + return rest.TransportFor(config) + } + c.mu.Lock() + defer c.mu.Unlock() + // See if we already have a custom transport for this config + key := tlsConfigKey(config) + if t, ok := c.transports[key]; ok { + return t, nil + } + restTransport, err := rest.TransportFor(config) + if err != nil { + return nil, err + } + c.transports[key] = restTransport + return restTransport, nil +} + +type tlsCacheKey struct { + certData string + keyData string `datapolicy:"secret-key"` +} + +func tlsConfigKey(c *rest.Config) tlsCacheKey { + return tlsCacheKey{ + certData: string(c.TLSClientConfig.CertData), + keyData: string(c.TLSClientConfig.KeyData), + } +} + +// NewAvailableConditionController returns a new AvailableConditionController. +func NewAvailableConditionController( + apiServiceInformer informers.APIServiceInformer, + serviceInformer v1informers.ServiceInformer, + endpointsInformer v1informers.EndpointsInformer, + apiServiceClient apiregistrationclient.APIServicesGetter, + proxyTransport *http.Transport, + proxyCurrentCertKeyContent certKeyFunc, + serviceResolver ServiceResolver, + egressSelector *egressselector.EgressSelector, +) (*AvailableConditionController, error) { + c := &AvailableConditionController{ + apiServiceClient: apiServiceClient, + apiServiceLister: apiServiceInformer.Lister(), + apiServiceSynced: apiServiceInformer.Informer().HasSynced, + serviceLister: serviceInformer.Lister(), + servicesSynced: serviceInformer.Informer().HasSynced, + endpointsLister: endpointsInformer.Lister(), + endpointsSynced: endpointsInformer.Informer().HasSynced, + serviceResolver: serviceResolver, + queue: workqueue.NewNamedRateLimitingQueue( + // We want a fairly tight requeue time. The controller listens to the API, but because it relies on the routability of the + // service network, it is possible for an external, non-watchable factor to affect availability. This keeps + // the maximum disruption time to a minimum, but it does prevent hot loops. + workqueue.NewItemExponentialFailureRateLimiter(5*time.Millisecond, 30*time.Second), + "AvailableConditionController"), + proxyCurrentCertKeyContent: proxyCurrentCertKeyContent, + tlsCache: &tlsTransportCache{transports: make(map[tlsCacheKey]http.RoundTripper)}, + metrics: newAvailabilityMetrics(), + } + + if egressSelector != nil { + networkContext := egressselector.Cluster.AsNetworkContext() + var egressDialer utilnet.DialFunc + egressDialer, err := egressSelector.Lookup(networkContext) + if err != nil { + return nil, err + } + c.dialContext = egressDialer + } else if proxyTransport != nil && proxyTransport.DialContext != nil { + c.dialContext = proxyTransport.DialContext + } + + // resync on this one because it is low cardinality and rechecking the actual discovery + // allows us to detect health in a more timely fashion when network connectivity to + // nodes is snipped, but the network still attempts to route there. See + // https://github.com/openshift/origin/issues/17159#issuecomment-341798063 + apiServiceInformer.Informer().AddEventHandlerWithResyncPeriod( + cache.ResourceEventHandlerFuncs{ + AddFunc: c.addAPIService, + UpdateFunc: c.updateAPIService, + DeleteFunc: c.deleteAPIService, + }, + 30*time.Second) + + serviceInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.addService, + UpdateFunc: c.updateService, + DeleteFunc: c.deleteService, + }) + + endpointsInformer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ + AddFunc: c.addEndpoints, + UpdateFunc: c.updateEndpoints, + DeleteFunc: c.deleteEndpoints, + }) + + c.syncFn = c.sync + + // TODO: decouple from legacyregistry + var err error + registerIntoLegacyRegistryOnce.Do(func() { + err = c.metrics.Register(legacyregistry.Register, legacyregistry.CustomRegister) + }) + if err != nil { + return nil, err + } + + return c, nil +} + +func (c *AvailableConditionController) sync(key string) error { + originalAPIService, err := c.apiServiceLister.Get(key) + if apierrors.IsNotFound(err) { + c.metrics.ForgetAPIService(key) + return nil + } + if err != nil { + return err + } + + // if a particular transport was specified, use that otherwise build one + // construct an http client that will ignore TLS verification (if someone owns the network and messes with your status + // that's not so bad) and sets a very short timeout. This is a best effort GET that provides no additional information + restConfig := &rest.Config{ + TLSClientConfig: rest.TLSClientConfig{ + Insecure: true, + }, + } + + if c.proxyCurrentCertKeyContent != nil { + proxyClientCert, proxyClientKey := c.proxyCurrentCertKeyContent() + + restConfig.TLSClientConfig.CertData = proxyClientCert + restConfig.TLSClientConfig.KeyData = proxyClientKey + } + if c.dialContext != nil { + restConfig.Dial = c.dialContext + } + // TLS config with customized dialer cannot be cached by the client-go + // tlsTransportCache. Use a local cache here to reduce the chance of + // the controller spamming idle connections with short-lived transports. + // NOTE: the cache works because we assume that the transports constructed + // by the controller only vary on the dynamic cert/key. + restTransport, err := c.tlsCache.get(restConfig) + if err != nil { + return err + } + discoveryClient := &http.Client{ + Transport: restTransport, + // the request should happen quickly. + Timeout: 5 * time.Second, + } + + apiService := originalAPIService.DeepCopy() + + availableCondition := apiregistrationv1.APIServiceCondition{ + Type: apiregistrationv1.Available, + Status: apiregistrationv1.ConditionTrue, + LastTransitionTime: metav1.Now(), + } + + // local API services are always considered available + if apiService.Spec.Service == nil { + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, apiregistrationv1apihelper.NewLocalAvailableAPIServiceCondition()) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } + + service, err := c.serviceLister.Services(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name) + if apierrors.IsNotFound(err) { + availableCondition.Status = apiregistrationv1.ConditionFalse + availableCondition.Reason = "ServiceNotFound" + availableCondition.Message = fmt.Sprintf("service/%s in %q is not present", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace) + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } else if err != nil { + availableCondition.Status = apiregistrationv1.ConditionUnknown + availableCondition.Reason = "ServiceAccessError" + availableCondition.Message = fmt.Sprintf("service/%s in %q cannot be checked due to: %v", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace, err) + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } + + if service.Spec.Type == v1.ServiceTypeClusterIP { + // if we have a cluster IP service, it must be listening on configured port and we can check that + servicePort := apiService.Spec.Service.Port + portName := "" + foundPort := false + for _, port := range service.Spec.Ports { + if port.Port == *servicePort { + foundPort = true + portName = port.Name + break + } + } + if !foundPort { + availableCondition.Status = apiregistrationv1.ConditionFalse + availableCondition.Reason = "ServicePortError" + availableCondition.Message = fmt.Sprintf("service/%s in %q is not listening on port %d", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace, *apiService.Spec.Service.Port) + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } + + endpoints, err := c.endpointsLister.Endpoints(apiService.Spec.Service.Namespace).Get(apiService.Spec.Service.Name) + if apierrors.IsNotFound(err) { + availableCondition.Status = apiregistrationv1.ConditionFalse + availableCondition.Reason = "EndpointsNotFound" + availableCondition.Message = fmt.Sprintf("cannot find endpoints for service/%s in %q", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace) + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } else if err != nil { + availableCondition.Status = apiregistrationv1.ConditionUnknown + availableCondition.Reason = "EndpointsAccessError" + availableCondition.Message = fmt.Sprintf("service/%s in %q cannot be checked due to: %v", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace, err) + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } + hasActiveEndpoints := false + outer: + for _, subset := range endpoints.Subsets { + if len(subset.Addresses) == 0 { + continue + } + for _, endpointPort := range subset.Ports { + if endpointPort.Name == portName { + hasActiveEndpoints = true + break outer + } + } + } + if !hasActiveEndpoints { + availableCondition.Status = apiregistrationv1.ConditionFalse + availableCondition.Reason = "MissingEndpoints" + availableCondition.Message = fmt.Sprintf("endpoints for service/%s in %q have no addresses with port name %q", apiService.Spec.Service.Name, apiService.Spec.Service.Namespace, portName) + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err := c.updateAPIServiceStatus(originalAPIService, apiService) + return err + } + } + // actually try to hit the discovery endpoint when it isn't local and when we're routing as a service. + if apiService.Spec.Service != nil && c.serviceResolver != nil { + attempts := 5 + results := make(chan error, attempts) + for i := 0; i < attempts; i++ { + go func() { + discoveryURL, err := c.serviceResolver.ResolveEndpoint(apiService.Spec.Service.Namespace, apiService.Spec.Service.Name, *apiService.Spec.Service.Port) + if err != nil { + results <- err + return + } + // render legacyAPIService health check path when it is delegated to a service + if apiService.Name == "v1." { + discoveryURL.Path = "/api/" + apiService.Spec.Version + } else { + discoveryURL.Path = "/apis/" + apiService.Spec.Group + "/" + apiService.Spec.Version + } + + errCh := make(chan error, 1) + go func() { + // be sure to check a URL that the aggregated API server is required to serve + newReq, err := http.NewRequest("GET", discoveryURL.String(), nil) + if err != nil { + errCh <- err + return + } + + // setting the system-masters identity ensures that we will always have access rights + transport.SetAuthProxyHeaders(newReq, "system:kube-aggregator", []string{"system:masters"}, nil) + resp, err := discoveryClient.Do(newReq) + if resp != nil { + resp.Body.Close() + // we should always been in the 200s or 300s + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices { + errCh <- fmt.Errorf("bad status from %v: %v", discoveryURL, resp.StatusCode) + return + } + } + + errCh <- err + }() + + select { + case err = <-errCh: + if err != nil { + results <- fmt.Errorf("failing or missing response from %v: %v", discoveryURL, err) + return + } + + // we had trouble with slow dial and DNS responses causing us to wait too long. + // we added this as insurance + case <-time.After(6 * time.Second): + results <- fmt.Errorf("timed out waiting for %v", discoveryURL) + return + } + + results <- nil + }() + } + + var lastError error + for i := 0; i < attempts; i++ { + lastError = <-results + // if we had at least one success, we are successful overall and we can return now + if lastError == nil { + break + } + } + + if lastError != nil { + availableCondition.Status = apiregistrationv1.ConditionFalse + availableCondition.Reason = "FailedDiscoveryCheck" + availableCondition.Message = lastError.Error() + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, updateErr := c.updateAPIServiceStatus(originalAPIService, apiService) + if updateErr != nil { + return updateErr + } + // force a requeue to make it very obvious that this will be retried at some point in the future + // along with other requeues done via service change, endpoint change, and resync + return lastError + } + } + + availableCondition.Reason = "Passed" + availableCondition.Message = "all checks passed" + apiregistrationv1apihelper.SetAPIServiceCondition(apiService, availableCondition) + _, err = c.updateAPIServiceStatus(originalAPIService, apiService) + return err +} + +// updateAPIServiceStatus only issues an update if a change is detected. We have a tight resync loop to quickly detect dead +// apiservices. Doing that means we don't want to quickly issue no-op updates. +func (c *AvailableConditionController) updateAPIServiceStatus(originalAPIService, newAPIService *apiregistrationv1.APIService) (*apiregistrationv1.APIService, error) { + // update this metric on every sync operation to reflect the actual state + c.setUnavailableGauge(newAPIService) + + if equality.Semantic.DeepEqual(originalAPIService.Status, newAPIService.Status) { + return newAPIService, nil + } + + orig := apiregistrationv1apihelper.GetAPIServiceConditionByType(originalAPIService, apiregistrationv1.Available) + now := apiregistrationv1apihelper.GetAPIServiceConditionByType(newAPIService, apiregistrationv1.Available) + unknown := apiregistrationv1.APIServiceCondition{ + Type: apiregistrationv1.Available, + Status: apiregistrationv1.ConditionUnknown, + } + if orig == nil { + orig = &unknown + } + if now == nil { + now = &unknown + } + if *orig != *now { + klog.V(2).InfoS("changing APIService availability", "name", newAPIService.Name, "oldStatus", orig.Status, "newStatus", now.Status, "message", now.Message, "reason", now.Reason) + } + + newAPIService, err := c.apiServiceClient.APIServices().UpdateStatus(context.TODO(), newAPIService, metav1.UpdateOptions{}) + if err != nil { + return nil, err + } + + c.setUnavailableCounter(originalAPIService, newAPIService) + return newAPIService, nil +} + +// Run starts the AvailableConditionController loop which manages the availability condition of API services. +func (c *AvailableConditionController) Run(workers int, stopCh <-chan struct{}) { + defer utilruntime.HandleCrash() + defer c.queue.ShutDown() + + klog.Info("Starting AvailableConditionController") + defer klog.Info("Shutting down AvailableConditionController") + + if !controllers.WaitForCacheSync("AvailableConditionController", stopCh, c.apiServiceSynced, c.servicesSynced, c.endpointsSynced) { + return + } + + for i := 0; i < workers; i++ { + go wait.Until(c.runWorker, time.Second, stopCh) + } + + <-stopCh +} + +func (c *AvailableConditionController) runWorker() { + for c.processNextWorkItem() { + } +} + +// processNextWorkItem deals with one key off the queue. It returns false when it's time to quit. +func (c *AvailableConditionController) processNextWorkItem() bool { + key, quit := c.queue.Get() + if quit { + return false + } + defer c.queue.Done(key) + + err := c.syncFn(key.(string)) + if err == nil { + c.queue.Forget(key) + return true + } + + utilruntime.HandleError(fmt.Errorf("%v failed with: %v", key, err)) + c.queue.AddRateLimited(key) + + return true +} + +func (c *AvailableConditionController) addAPIService(obj interface{}) { + castObj := obj.(*apiregistrationv1.APIService) + klog.V(4).Infof("Adding %s", castObj.Name) + if castObj.Spec.Service != nil { + c.rebuildAPIServiceCache() + } + c.queue.Add(castObj.Name) +} + +func (c *AvailableConditionController) updateAPIService(oldObj, newObj interface{}) { + castObj := newObj.(*apiregistrationv1.APIService) + oldCastObj := oldObj.(*apiregistrationv1.APIService) + klog.V(4).Infof("Updating %s", oldCastObj.Name) + if !reflect.DeepEqual(castObj.Spec.Service, oldCastObj.Spec.Service) { + c.rebuildAPIServiceCache() + } + c.queue.Add(oldCastObj.Name) +} + +func (c *AvailableConditionController) deleteAPIService(obj interface{}) { + castObj, ok := obj.(*apiregistrationv1.APIService) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + klog.Errorf("Couldn't get object from tombstone %#v", obj) + return + } + castObj, ok = tombstone.Obj.(*apiregistrationv1.APIService) + if !ok { + klog.Errorf("Tombstone contained object that is not expected %#v", obj) + return + } + } + klog.V(4).Infof("Deleting %q", castObj.Name) + if castObj.Spec.Service != nil { + c.rebuildAPIServiceCache() + } + c.queue.Add(castObj.Name) +} + +func (c *AvailableConditionController) getAPIServicesFor(obj runtime.Object) []string { + metadata, err := meta.Accessor(obj) + if err != nil { + utilruntime.HandleError(err) + return nil + } + c.cacheLock.RLock() + defer c.cacheLock.RUnlock() + return c.cache[metadata.GetNamespace()][metadata.GetName()] +} + +// if the service/endpoint handler wins the race against the cache rebuilding, it may queue a no-longer-relevant apiservice +// (which will get processed an extra time - this doesn't matter), +// and miss a newly relevant apiservice (which will get queued by the apiservice handler) +func (c *AvailableConditionController) rebuildAPIServiceCache() { + apiServiceList, _ := c.apiServiceLister.List(labels.Everything()) + newCache := map[string]map[string][]string{} + for _, apiService := range apiServiceList { + if apiService.Spec.Service == nil { + continue + } + if newCache[apiService.Spec.Service.Namespace] == nil { + newCache[apiService.Spec.Service.Namespace] = map[string][]string{} + } + newCache[apiService.Spec.Service.Namespace][apiService.Spec.Service.Name] = append(newCache[apiService.Spec.Service.Namespace][apiService.Spec.Service.Name], apiService.Name) + } + + c.cacheLock.Lock() + defer c.cacheLock.Unlock() + c.cache = newCache +} + +// TODO, think of a way to avoid checking on every service manipulation + +func (c *AvailableConditionController) addService(obj interface{}) { + for _, apiService := range c.getAPIServicesFor(obj.(*v1.Service)) { + c.queue.Add(apiService) + } +} + +func (c *AvailableConditionController) updateService(obj, _ interface{}) { + for _, apiService := range c.getAPIServicesFor(obj.(*v1.Service)) { + c.queue.Add(apiService) + } +} + +func (c *AvailableConditionController) deleteService(obj interface{}) { + castObj, ok := obj.(*v1.Service) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + klog.Errorf("Couldn't get object from tombstone %#v", obj) + return + } + castObj, ok = tombstone.Obj.(*v1.Service) + if !ok { + klog.Errorf("Tombstone contained object that is not expected %#v", obj) + return + } + } + for _, apiService := range c.getAPIServicesFor(castObj) { + c.queue.Add(apiService) + } +} + +func (c *AvailableConditionController) addEndpoints(obj interface{}) { + for _, apiService := range c.getAPIServicesFor(obj.(*v1.Endpoints)) { + c.queue.Add(apiService) + } +} + +func (c *AvailableConditionController) updateEndpoints(obj, _ interface{}) { + for _, apiService := range c.getAPIServicesFor(obj.(*v1.Endpoints)) { + c.queue.Add(apiService) + } +} + +func (c *AvailableConditionController) deleteEndpoints(obj interface{}) { + castObj, ok := obj.(*v1.Endpoints) + if !ok { + tombstone, ok := obj.(cache.DeletedFinalStateUnknown) + if !ok { + klog.Errorf("Couldn't get object from tombstone %#v", obj) + return + } + castObj, ok = tombstone.Obj.(*v1.Endpoints) + if !ok { + klog.Errorf("Tombstone contained object that is not expected %#v", obj) + return + } + } + for _, apiService := range c.getAPIServicesFor(castObj) { + c.queue.Add(apiService) + } +} + +// setUnavailableGauge set the metrics so that it reflect the current state base on availability of the given service +func (c *AvailableConditionController) setUnavailableGauge(newAPIService *apiregistrationv1.APIService) { + if apiregistrationv1apihelper.IsAPIServiceConditionTrue(newAPIService, apiregistrationv1.Available) { + c.metrics.SetAPIServiceAvailable(newAPIService.Name) + return + } + + c.metrics.SetAPIServiceUnavailable(newAPIService.Name) +} + +// setUnavailableCounter increases the metrics only if the given service is unavailable and its APIServiceCondition has changed +func (c *AvailableConditionController) setUnavailableCounter(originalAPIService, newAPIService *apiregistrationv1.APIService) { + wasAvailable := apiregistrationv1apihelper.IsAPIServiceConditionTrue(originalAPIService, apiregistrationv1.Available) + isAvailable := apiregistrationv1apihelper.IsAPIServiceConditionTrue(newAPIService, apiregistrationv1.Available) + statusChanged := isAvailable != wasAvailable + + if statusChanged && !isAvailable { + reason := "UnknownReason" + if newCondition := apiregistrationv1apihelper.GetAPIServiceConditionByType(newAPIService, apiregistrationv1.Available); newCondition != nil { + reason = newCondition.Reason + } + c.metrics.UnavailableCounter(newAPIService.Name, reason).Inc() + } +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/controllers/status/metrics.go b/vendor/k8s.io/kube-aggregator/pkg/controllers/status/metrics.go new file mode 100644 index 000000000000..b0653f5988b7 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/controllers/status/metrics.go @@ -0,0 +1,150 @@ +/* +Copyright 2018 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 ( + "sync" + + "k8s.io/component-base/metrics" +) + +/* + * By default, all the following metrics are defined as falling under + * ALPHA stability level https://github.com/kubernetes/enhancements/blob/master/keps/sig-instrumentation/1209-metrics-stability/kubernetes-control-plane-metrics-stability.md#stability-classes) + * + * Promoting the stability level of the metric is a responsibility of the component owner, since it + * involves explicitly acknowledging support for the metric across multiple releases, in accordance with + * the metric stability policy. + */ +var ( + unavailableGaugeDesc = metrics.NewDesc( + "aggregator_unavailable_apiservice", + "Gauge of APIServices which are marked as unavailable broken down by APIService name.", + []string{"name"}, + nil, + metrics.ALPHA, + "", + ) +) + +type availabilityMetrics struct { + unavailableCounter *metrics.CounterVec + + *availabilityCollector +} + +func newAvailabilityMetrics() *availabilityMetrics { + return &availabilityMetrics{ + unavailableCounter: metrics.NewCounterVec( + &metrics.CounterOpts{ + Name: "aggregator_unavailable_apiservice_total", + Help: "Counter of APIServices which are marked as unavailable broken down by APIService name and reason.", + StabilityLevel: metrics.ALPHA, + }, + []string{"name", "reason"}, + ), + availabilityCollector: newAvailabilityCollector(), + } +} + +// Register registers apiservice availability metrics. +func (m *availabilityMetrics) Register( + registrationFunc func(metrics.Registerable) error, + customRegistrationFunc func(metrics.StableCollector) error, +) error { + err := registrationFunc(m.unavailableCounter) + if err != nil { + return err + } + + err = customRegistrationFunc(m.availabilityCollector) + if err != nil { + return err + } + + return nil +} + +// UnavailableCounter returns a counter to track apiservices marked as unavailable. +func (m *availabilityMetrics) UnavailableCounter(apiServiceName, reason string) metrics.CounterMetric { + return m.unavailableCounter.WithLabelValues(apiServiceName, reason) +} + +type availabilityCollector struct { + metrics.BaseStableCollector + + mtx sync.RWMutex + availabilities map[string]bool +} + +// Check if apiServiceStatusCollector implements necessary interface. +var _ metrics.StableCollector = &availabilityCollector{} + +func newAvailabilityCollector() *availabilityCollector { + return &availabilityCollector{ + availabilities: make(map[string]bool), + } +} + +// DescribeWithStability implements the metrics.StableCollector interface. +func (c *availabilityCollector) DescribeWithStability(ch chan<- *metrics.Desc) { + ch <- unavailableGaugeDesc +} + +// CollectWithStability implements the metrics.StableCollector interface. +func (c *availabilityCollector) CollectWithStability(ch chan<- metrics.Metric) { + c.mtx.RLock() + defer c.mtx.RUnlock() + + for apiServiceName, isAvailable := range c.availabilities { + gaugeValue := 1.0 + if isAvailable { + gaugeValue = 0.0 + } + ch <- metrics.NewLazyConstMetric( + unavailableGaugeDesc, + metrics.GaugeValue, + gaugeValue, + apiServiceName, + ) + } +} + +// SetAPIServiceAvailable sets the given apiservice availability gauge to available. +func (c *availabilityCollector) SetAPIServiceAvailable(apiServiceKey string) { + c.setAPIServiceAvailability(apiServiceKey, true) +} + +// SetAPIServiceUnavailable sets the given apiservice availability gauge to unavailable. +func (c *availabilityCollector) SetAPIServiceUnavailable(apiServiceKey string) { + c.setAPIServiceAvailability(apiServiceKey, false) +} + +func (c *availabilityCollector) setAPIServiceAvailability(apiServiceKey string, availability bool) { + c.mtx.Lock() + defer c.mtx.Unlock() + + c.availabilities[apiServiceKey] = availability +} + +// ForgetAPIService removes the availability gauge of the given apiservice. +func (c *availabilityCollector) ForgetAPIService(apiServiceKey string) { + c.mtx.Lock() + defer c.mtx.Unlock() + + delete(c.availabilities, apiServiceKey) +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go b/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go new file mode 100644 index 000000000000..d20573ed3681 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/etcd/etcd.go @@ -0,0 +1,171 @@ +/* +Copyright 2016 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 etcd + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/api/meta" + metatable "k8s.io/apimachinery/pkg/api/meta/table" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/kube-aggregator/pkg/apis/apiregistration" + "k8s.io/kube-aggregator/pkg/registry/apiservice" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" +) + +// REST implements a RESTStorage for API services against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against API services. +func NewREST(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) *REST { + strategy := apiservice.NewStrategy(scheme) + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &apiregistration.APIService{} }, + NewListFunc: func() runtime.Object { return &apiregistration.APIServiceList{} }, + PredicateFunc: apiservice.MatchAPIService, + DefaultQualifiedResource: apiregistration.Resource("apiservices"), + + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, + ResetFieldsStrategy: strategy, + + // TODO: define table converter that exposes more than name/creation timestamp + TableConvertor: rest.NewDefaultTableConvertor(apiregistration.Resource("apiservices")), + } + options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: apiservice.GetAttrs} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + return &REST{store} +} + +// Implement CategoriesProvider +var _ rest.CategoriesProvider = &REST{} + +// Categories implements the CategoriesProvider interface. Returns a list of categories a resource is part of. +func (c *REST) Categories() []string { + return []string{"api-extensions"} +} + +var swaggerMetadataDescriptions = metav1.ObjectMeta{}.SwaggerDoc() + +// ConvertToTable implements the TableConvertor interface for REST. +func (c *REST) ConvertToTable(ctx context.Context, obj runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { + table := &metav1.Table{ + ColumnDefinitions: []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name", Description: swaggerMetadataDescriptions["name"]}, + {Name: "Service", Type: "string", Description: "The reference to the service that hosts this API endpoint."}, + {Name: "Available", Type: "string", Description: "Whether this service is available."}, + {Name: "Age", Type: "string", Description: swaggerMetadataDescriptions["creationTimestamp"]}, + }, + } + if m, err := meta.ListAccessor(obj); err == nil { + table.ResourceVersion = m.GetResourceVersion() + table.Continue = m.GetContinue() + table.RemainingItemCount = m.GetRemainingItemCount() + } else { + if m, err := meta.CommonAccessor(obj); err == nil { + table.ResourceVersion = m.GetResourceVersion() + } + } + + var err error + table.Rows, err = metatable.MetaToTableRow(obj, func(obj runtime.Object, m metav1.Object, name, age string) ([]interface{}, error) { + svc := obj.(*apiregistration.APIService) + service := "Local" + if svc.Spec.Service != nil { + service = fmt.Sprintf("%s/%s", svc.Spec.Service.Namespace, svc.Spec.Service.Name) + } + status := string(apiregistration.ConditionUnknown) + if condition := getCondition(svc.Status.Conditions, "Available"); condition != nil { + switch { + case condition.Status == apiregistration.ConditionTrue: + status = string(condition.Status) + case len(condition.Reason) > 0: + status = fmt.Sprintf("%s (%s)", condition.Status, condition.Reason) + default: + status = string(condition.Status) + } + } + return []interface{}{name, service, status, age}, nil + }) + return table, err +} + +func getCondition(conditions []apiregistration.APIServiceCondition, conditionType apiregistration.APIServiceConditionType) *apiregistration.APIServiceCondition { + for i, condition := range conditions { + if condition.Type == conditionType { + return &conditions[i] + } + } + return nil +} + +// NewStatusREST makes a RESTStorage for status that has more limited options. +// It is based on the original REST so that we can share the same underlying store +func NewStatusREST(scheme *runtime.Scheme, rest *REST) *StatusREST { + strategy := apiservice.NewStatusStrategy(scheme) + statusStore := *rest.Store + statusStore.CreateStrategy = nil + statusStore.DeleteStrategy = nil + statusStore.UpdateStrategy = strategy + statusStore.ResetFieldsStrategy = strategy + return &StatusREST{store: &statusStore} +} + +// StatusREST implements the REST endpoint for changing the status of an APIService. +type StatusREST struct { + store *genericregistry.Store +} + +var _ = rest.Patcher(&StatusREST{}) + +// New creates a new APIService object. +func (r *StatusREST) New() runtime.Object { + return &apiregistration.APIService{} +} + +// Destroy cleans up resources on shutdown. +func (r *StatusREST) Destroy() { + // Given that underlying store is shared with REST, + // we don't destroy it here explicitly. +} + +// Get retrieves the object from the storage. It is required to support Patch. +func (r *StatusREST) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { + return r.store.Get(ctx, name, options) +} + +// Update alters the status subset of an object. +func (r *StatusREST) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) { + // We are explicitly setting forceAllowCreate to false in the call to the underlying storage because + // subresources should never allow create on update. + return r.store.Update(ctx, name, objInfo, createValidation, updateValidation, false, options) +} + +// GetResetFields implements rest.ResetFieldsStrategy +func (r *StatusREST) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + return r.store.GetResetFields() +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/rest/storage_apiservice.go b/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/rest/storage_apiservice.go new file mode 100644 index 000000000000..3c159251c465 --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/rest/storage_apiservice.go @@ -0,0 +1,49 @@ +/* +Copyright 2018 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 rest + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "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" + + "k8s.io/kube-aggregator/pkg/apis/apiregistration" + v1 "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1" + aggregatorscheme "k8s.io/kube-aggregator/pkg/apiserver/scheme" + apiservicestorage "k8s.io/kube-aggregator/pkg/registry/apiservice/etcd" +) + +// NewRESTStorage returns an APIGroupInfo object that will work against apiservice. +func NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter, shouldServeBeta bool) genericapiserver.APIGroupInfo { + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apiregistration.GroupName, aggregatorscheme.Scheme, metav1.ParameterCodec, aggregatorscheme.Codecs) + + storage := map[string]rest.Storage{} + + if resource := "apiservices"; apiResourceConfigSource.ResourceEnabled(v1.SchemeGroupVersion.WithResource(resource)) { + apiServiceREST := apiservicestorage.NewREST(aggregatorscheme.Scheme, restOptionsGetter) + storage[resource] = apiServiceREST + storage[resource+"/status"] = apiservicestorage.NewStatusREST(aggregatorscheme.Scheme, apiServiceREST) + } + + if len(storage) > 0 { + apiGroupInfo.VersionedResourcesStorageMap["v1"] = storage + } + + return apiGroupInfo +} diff --git a/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go b/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go new file mode 100644 index 000000000000..5e368c82b2ac --- /dev/null +++ b/vendor/k8s.io/kube-aggregator/pkg/registry/apiservice/strategy.go @@ -0,0 +1,196 @@ +/* +Copyright 2016 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 apiservice + +import ( + "context" + "fmt" + + "k8s.io/apimachinery/pkg/fields" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/names" + + "k8s.io/kube-aggregator/pkg/apis/apiregistration" + "k8s.io/kube-aggregator/pkg/apis/apiregistration/validation" + "sigs.k8s.io/structured-merge-diff/v4/fieldpath" +) + +type apiServerStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// apiServerStrategy must implement rest.RESTCreateUpdateStrategy +var _ rest.RESTCreateUpdateStrategy = apiServerStrategy{} +var Strategy = apiServerStrategy{} + +// NewStrategy creates a new apiServerStrategy. +func NewStrategy(typer runtime.ObjectTyper) rest.CreateUpdateResetFieldsStrategy { + return apiServerStrategy{typer, names.SimpleNameGenerator} +} + +func (apiServerStrategy) NamespaceScoped() bool { + return false +} + +func (apiServerStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + "apiregistration.k8s.io/v1": fieldpath.NewSet( + fieldpath.MakePathOrDie("status"), + ), + "apiregistration.k8s.io/v1beta1": fieldpath.NewSet( + fieldpath.MakePathOrDie("status"), + ), + } + + return fields +} + +func (apiServerStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + apiservice := obj.(*apiregistration.APIService) + apiservice.Status = apiregistration.APIServiceStatus{} + + // mark local API services as immediately available on create + if apiservice.Spec.Service == nil { + apiregistration.SetAPIServiceCondition(apiservice, apiregistration.NewLocalAvailableAPIServiceCondition()) + } +} + +func (apiServerStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newAPIService := obj.(*apiregistration.APIService) + oldAPIService := old.(*apiregistration.APIService) + newAPIService.Status = oldAPIService.Status +} + +func (apiServerStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + return validation.ValidateAPIService(obj.(*apiregistration.APIService)) +} + +// WarningsOnCreate returns warnings for the creation of the given object. +func (apiServerStrategy) WarningsOnCreate(ctx context.Context, obj runtime.Object) []string { + return nil +} + +func (apiServerStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (apiServerStrategy) AllowUnconditionalUpdate() bool { + return false +} + +func (apiServerStrategy) Canonicalize(obj runtime.Object) { +} + +func (apiServerStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateAPIServiceUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) +} + +// WarningsOnUpdate returns warnings for the given update. +func (apiServerStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + +type apiServerStatusStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// NewStatusStrategy creates a new apiServerStatusStrategy. +func NewStatusStrategy(typer runtime.ObjectTyper) rest.UpdateResetFieldsStrategy { + return apiServerStatusStrategy{typer, names.SimpleNameGenerator} +} + +func (apiServerStatusStrategy) NamespaceScoped() bool { + return false +} + +func (apiServerStatusStrategy) GetResetFields() map[fieldpath.APIVersion]*fieldpath.Set { + fields := map[fieldpath.APIVersion]*fieldpath.Set{ + "apiregistration.k8s.io/v1": fieldpath.NewSet( + fieldpath.MakePathOrDie("spec"), + fieldpath.MakePathOrDie("metadata"), + ), + "apiregistration.k8s.io/v1beta1": fieldpath.NewSet( + fieldpath.MakePathOrDie("spec"), + fieldpath.MakePathOrDie("metadata"), + ), + } + + return fields +} + +func (apiServerStatusStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newAPIService := obj.(*apiregistration.APIService) + oldAPIService := old.(*apiregistration.APIService) + newAPIService.Spec = oldAPIService.Spec + newAPIService.Labels = oldAPIService.Labels + newAPIService.Annotations = oldAPIService.Annotations + newAPIService.Finalizers = oldAPIService.Finalizers + newAPIService.OwnerReferences = oldAPIService.OwnerReferences +} + +func (apiServerStatusStrategy) AllowCreateOnUpdate() bool { + return false +} + +func (apiServerStatusStrategy) AllowUnconditionalUpdate() bool { + return false +} + +// Canonicalize normalizes the object after validation. +func (apiServerStatusStrategy) Canonicalize(obj runtime.Object) { +} + +// ValidateUpdate validates an update of apiServerStatusStrategy. +func (apiServerStatusStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + return validation.ValidateAPIServiceStatusUpdate(obj.(*apiregistration.APIService), old.(*apiregistration.APIService)) +} + +// WarningsOnUpdate returns warnings for the given update. +func (apiServerStatusStrategy) WarningsOnUpdate(ctx context.Context, obj, old runtime.Object) []string { + return nil +} + +// GetAttrs returns the labels and fields of an API server for filtering purposes. +func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) { + apiserver, ok := obj.(*apiregistration.APIService) + if !ok { + return nil, nil, fmt.Errorf("given object is not a APIService") + } + return labels.Set(apiserver.ObjectMeta.Labels), ToSelectableFields(apiserver), nil +} + +// MatchAPIService is the filter used by the generic etcd backend to watch events +// from etcd to clients of the apiserver only interested in specific labels/fields. +func MatchAPIService(label labels.Selector, field fields.Selector) storage.SelectionPredicate { + return storage.SelectionPredicate{ + Label: label, + Field: field, + GetAttrs: GetAttrs, + } +} + +// ToSelectableFields returns a field set that represents the object. +func ToSelectableFields(obj *apiregistration.APIService) fields.Set { + return generic.ObjectMetaFieldsSet(&obj.ObjectMeta, true) +} diff --git a/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go b/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go new file mode 100644 index 000000000000..cdb7d9ef9942 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/aggregator/aggregator.go @@ -0,0 +1,377 @@ +/* +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 aggregator + +import ( + "fmt" + "reflect" + "sort" + "strings" + + "k8s.io/kube-openapi/pkg/validation/spec" + + "k8s.io/kube-openapi/pkg/schemamutation" + "k8s.io/kube-openapi/pkg/util" +) + +const gvkKey = "x-kubernetes-group-version-kind" + +// usedDefinitionForSpec returns a map with all used definitions in the provided spec as keys and true as values. +func usedDefinitionForSpec(root *spec.Swagger) map[string]bool { + usedDefinitions := map[string]bool{} + walkOnAllReferences(func(ref *spec.Ref) { + if refStr := ref.String(); refStr != "" && strings.HasPrefix(refStr, definitionPrefix) { + usedDefinitions[refStr[len(definitionPrefix):]] = true + } + }, root) + return usedDefinitions +} + +// FilterSpecByPaths removes unnecessary paths and definitions used by those paths. +// i.e. if a Path removed by this function, all definitions used by it and not used +// anywhere else will also be removed. +func FilterSpecByPaths(sp *spec.Swagger, keepPathPrefixes []string) { + *sp = *FilterSpecByPathsWithoutSideEffects(sp, keepPathPrefixes) +} + +// FilterSpecByPathsWithoutSideEffects removes unnecessary paths and definitions used by those paths. +// i.e. if a Path removed by this function, all definitions used by it and not used +// anywhere else will also be removed. +// It does not modify the input, but the output shares data structures with the input. +func FilterSpecByPathsWithoutSideEffects(sp *spec.Swagger, keepPathPrefixes []string) *spec.Swagger { + if sp.Paths == nil { + return sp + } + + // Walk all references to find all used definitions. This function + // want to only deal with unused definitions resulted from filtering paths. + // Thus a definition will be removed only if it has been used before but + // it is unused because of a path prune. + initialUsedDefinitions := usedDefinitionForSpec(sp) + + // First remove unwanted paths + prefixes := util.NewTrie(keepPathPrefixes) + ret := *sp + ret.Paths = &spec.Paths{ + VendorExtensible: sp.Paths.VendorExtensible, + Paths: map[string]spec.PathItem{}, + } + for path, pathItem := range sp.Paths.Paths { + if !prefixes.HasPrefix(path) { + continue + } + ret.Paths.Paths[path] = pathItem + } + + // Walk all references to find all definition references. + usedDefinitions := usedDefinitionForSpec(&ret) + + // Remove unused definitions + ret.Definitions = spec.Definitions{} + for k, v := range sp.Definitions { + if usedDefinitions[k] || !initialUsedDefinitions[k] { + ret.Definitions[k] = v + } + } + + return &ret +} + +type rename struct { + from, to string +} + +// renameDefinition renames references, without mutating the input. +// The output might share data structures with the input. +func renameDefinition(s *spec.Swagger, renames map[string]string) *spec.Swagger { + refRenames := make(map[string]string, len(renames)) + foundOne := false + for k, v := range renames { + refRenames[definitionPrefix+k] = definitionPrefix + v + if _, ok := s.Definitions[k]; ok { + foundOne = true + } + } + + if !foundOne { + return s + } + + ret := &spec.Swagger{} + *ret = *s + + ret = schemamutation.ReplaceReferences(func(ref *spec.Ref) *spec.Ref { + refName := ref.String() + if newRef, found := refRenames[refName]; found { + ret := spec.MustCreateRef(newRef) + return &ret + } + return ref + }, ret) + + renamedDefinitions := make(spec.Definitions, len(ret.Definitions)) + for k, v := range ret.Definitions { + if newRef, found := renames[k]; found { + k = newRef + } + renamedDefinitions[k] = v + } + ret.Definitions = renamedDefinitions + + return ret +} + +// MergeSpecsIgnorePathConflict is the same as MergeSpecs except it will ignore any path +// conflicts by keeping the paths of destination. It will rename definition conflicts. +// The source is not mutated. +func MergeSpecsIgnorePathConflict(dest, source *spec.Swagger) error { + return mergeSpecs(dest, source, true, true) +} + +// MergeSpecsFailOnDefinitionConflict is differ from MergeSpecs as it fails if there is +// a definition conflict. +// The source is not mutated. +func MergeSpecsFailOnDefinitionConflict(dest, source *spec.Swagger) error { + return mergeSpecs(dest, source, false, false) +} + +// MergeSpecs copies paths and definitions from source to dest, rename definitions if needed. +// dest will be mutated, and source will not be changed. It will fail on path conflicts. +// The source is not mutated. +func MergeSpecs(dest, source *spec.Swagger) error { + return mergeSpecs(dest, source, true, false) +} + +// mergeSpecs merges source into dest while resolving conflicts. +// The source is not mutated. +func mergeSpecs(dest, source *spec.Swagger, renameModelConflicts, ignorePathConflicts bool) (err error) { + // Paths may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering). + if source.Paths == nil { + // When a source spec does not have any path, that means none of the definitions + // are used thus we should not do anything + return nil + } + if dest.Paths == nil { + dest.Paths = &spec.Paths{} + } + if ignorePathConflicts { + keepPaths := []string{} + hasConflictingPath := false + for k := range source.Paths.Paths { + if _, found := dest.Paths.Paths[k]; !found { + keepPaths = append(keepPaths, k) + } else { + hasConflictingPath = true + } + } + if len(keepPaths) == 0 { + // There is nothing to merge. All paths are conflicting. + return nil + } + if hasConflictingPath { + source = FilterSpecByPathsWithoutSideEffects(source, keepPaths) + } + } + + // Check for model conflicts and rename to make definitions conflict-free (modulo different GVKs) + usedNames := map[string]bool{} + for k := range dest.Definitions { + usedNames[k] = true + } + renames := map[string]string{} +DEFINITIONLOOP: + for k, v := range source.Definitions { + existing, found := dest.Definitions[k] + if !found || deepEqualDefinitionsModuloGVKs(&existing, &v) { + // skip for now, we copy them after the rename loop + continue + } + + if !renameModelConflicts { + return fmt.Errorf("model name conflict in merging OpenAPI spec: %s", k) + } + + // Reuse previously renamed model if one exists + var newName string + i := 1 + for found { + i++ + newName = fmt.Sprintf("%s_v%d", k, i) + existing, found = dest.Definitions[newName] + if found && deepEqualDefinitionsModuloGVKs(&existing, &v) { + renames[k] = newName + continue DEFINITIONLOOP + } + } + + _, foundInSource := source.Definitions[newName] + for usedNames[newName] || foundInSource { + i++ + newName = fmt.Sprintf("%s_v%d", k, i) + _, foundInSource = source.Definitions[newName] + } + renames[k] = newName + usedNames[newName] = true + } + source = renameDefinition(source, renames) + + // now without conflict (modulo different GVKs), copy definitions to dest + for k, v := range source.Definitions { + if existing, found := dest.Definitions[k]; !found { + if dest.Definitions == nil { + dest.Definitions = spec.Definitions{} + } + dest.Definitions[k] = v + } else if merged, changed, err := mergedGVKs(&existing, &v); err != nil { + return err + } else if changed { + existing.Extensions[gvkKey] = merged + } + } + + // Check for path conflicts + for k, v := range source.Paths.Paths { + if _, found := dest.Paths.Paths[k]; found { + return fmt.Errorf("unable to merge: duplicated path %s", k) + } + // PathItem may be empty, due to [ACL constraints](http://goo.gl/8us55a#securityFiltering). + if dest.Paths.Paths == nil { + dest.Paths.Paths = map[string]spec.PathItem{} + } + dest.Paths.Paths[k] = v + } + + return nil +} + +// deepEqualDefinitionsModuloGVKs compares s1 and s2, but ignores the x-kubernetes-group-version-kind extension. +func deepEqualDefinitionsModuloGVKs(s1, s2 *spec.Schema) bool { + if s1 == nil { + return s2 == nil + } else if s2 == nil { + return false + } + if !reflect.DeepEqual(s1.Extensions, s2.Extensions) { + for k, v := range s1.Extensions { + if k == gvkKey { + continue + } + if !reflect.DeepEqual(v, s2.Extensions[k]) { + return false + } + } + len1 := len(s1.Extensions) + len2 := len(s2.Extensions) + if _, found := s1.Extensions[gvkKey]; found { + len1-- + } + if _, found := s2.Extensions[gvkKey]; found { + len2-- + } + if len1 != len2 { + return false + } + + if s1.Extensions != nil { + shallowCopy := *s1 + s1 = &shallowCopy + s1.Extensions = nil + } + if s2.Extensions != nil { + shallowCopy := *s2 + s2 = &shallowCopy + s2.Extensions = nil + } + } + + return reflect.DeepEqual(s1, s2) +} + +// mergedGVKs merges the x-kubernetes-group-version-kind slices and returns the result, and whether +// s1's x-kubernetes-group-version-kind slice was changed at all. +func mergedGVKs(s1, s2 *spec.Schema) (interface{}, bool, error) { + gvk1, found1 := s1.Extensions[gvkKey] + gvk2, found2 := s2.Extensions[gvkKey] + + if !found1 { + return gvk2, found2, nil + } + if !found2 { + return gvk1, false, nil + } + + slice1, ok := gvk1.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("expected slice of GroupVersionKinds, got: %+v", slice1) + } + slice2, ok := gvk2.([]interface{}) + if !ok { + return nil, false, fmt.Errorf("expected slice of GroupVersionKinds, got: %+v", slice2) + } + + ret := make([]interface{}, len(slice1), len(slice1)+len(slice2)) + keys := make([]string, 0, len(slice1)+len(slice2)) + copy(ret, slice1) + seen := make(map[string]bool, len(slice1)) + for _, x := range slice1 { + gvk, ok := x.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf(`expected {"group": , "kind": , "version": }, got: %#v`, x) + } + k := fmt.Sprintf("%s/%s.%s", gvk["group"], gvk["version"], gvk["kind"]) + keys = append(keys, k) + seen[k] = true + } + changed := false + for _, x := range slice2 { + gvk, ok := x.(map[string]interface{}) + if !ok { + return nil, false, fmt.Errorf(`expected {"group": , "kind": , "version": }, got: %#v`, x) + } + k := fmt.Sprintf("%s/%s.%s", gvk["group"], gvk["version"], gvk["kind"]) + if seen[k] { + continue + } + ret = append(ret, x) + keys = append(keys, k) + changed = true + } + + if changed { + sort.Sort(byKeys{ret, keys}) + } + + return ret, changed, nil +} + +type byKeys struct { + values []interface{} + keys []string +} + +func (b byKeys) Len() int { + return len(b.values) +} + +func (b byKeys) Less(i, j int) bool { + return b.keys[i] < b.keys[j] +} + +func (b byKeys) Swap(i, j int) { + b.values[i], b.values[j] = b.values[j], b.values[i] + b.keys[i], b.keys[j] = b.keys[j], b.keys[i] +} diff --git a/vendor/k8s.io/kube-openapi/pkg/aggregator/walker.go b/vendor/k8s.io/kube-openapi/pkg/aggregator/walker.go new file mode 100644 index 000000000000..351053f86414 --- /dev/null +++ b/vendor/k8s.io/kube-openapi/pkg/aggregator/walker.go @@ -0,0 +1,162 @@ +/* +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 aggregator + +import ( + "strings" + + "k8s.io/kube-openapi/pkg/validation/spec" +) + +const ( + definitionPrefix = "#/definitions/" +) + +// Run a readonlyReferenceWalker method on all references of an OpenAPI spec +type readonlyReferenceWalker struct { + // walkRefCallback will be called on each reference. The input will never be nil. + walkRefCallback func(ref *spec.Ref) + + // The spec to walk through. + root *spec.Swagger +} + +// walkOnAllReferences recursively walks on all references, while following references into definitions. +// it calls walkRef on each found reference. +func walkOnAllReferences(walkRef func(ref *spec.Ref), root *spec.Swagger) { + alreadyVisited := map[string]bool{} + + walker := &readonlyReferenceWalker{ + root: root, + } + walker.walkRefCallback = func(ref *spec.Ref) { + walkRef(ref) + + refStr := ref.String() + if refStr == "" || !strings.HasPrefix(refStr, definitionPrefix) { + return + } + defName := refStr[len(definitionPrefix):] + + if _, found := root.Definitions[defName]; found && !alreadyVisited[refStr] { + alreadyVisited[refStr] = true + def := root.Definitions[defName] + walker.walkSchema(&def) + } + } + walker.Start() +} + +func (s *readonlyReferenceWalker) walkSchema(schema *spec.Schema) { + if schema == nil { + return + } + s.walkRefCallback(&schema.Ref) + var v *spec.Schema + if len(schema.Definitions)+len(schema.Properties)+len(schema.PatternProperties) > 0 { + v = &spec.Schema{} + } + for k := range schema.Definitions { + *v = schema.Definitions[k] + s.walkSchema(v) + } + for k := range schema.Properties { + *v = schema.Properties[k] + s.walkSchema(v) + } + for k := range schema.PatternProperties { + *v = schema.PatternProperties[k] + s.walkSchema(v) + } + for i := range schema.AllOf { + s.walkSchema(&schema.AllOf[i]) + } + for i := range schema.AnyOf { + s.walkSchema(&schema.AnyOf[i]) + } + for i := range schema.OneOf { + s.walkSchema(&schema.OneOf[i]) + } + if schema.Not != nil { + s.walkSchema(schema.Not) + } + if schema.AdditionalProperties != nil && schema.AdditionalProperties.Schema != nil { + s.walkSchema(schema.AdditionalProperties.Schema) + } + if schema.AdditionalItems != nil && schema.AdditionalItems.Schema != nil { + s.walkSchema(schema.AdditionalItems.Schema) + } + if schema.Items != nil { + if schema.Items.Schema != nil { + s.walkSchema(schema.Items.Schema) + } + for i := range schema.Items.Schemas { + s.walkSchema(&schema.Items.Schemas[i]) + } + } +} + +func (s *readonlyReferenceWalker) walkParams(params []spec.Parameter) { + if params == nil { + return + } + for _, param := range params { + s.walkRefCallback(¶m.Ref) + s.walkSchema(param.Schema) + if param.Items != nil { + s.walkRefCallback(¶m.Items.Ref) + } + } +} + +func (s *readonlyReferenceWalker) walkResponse(resp *spec.Response) { + if resp == nil { + return + } + s.walkRefCallback(&resp.Ref) + s.walkSchema(resp.Schema) +} + +func (s *readonlyReferenceWalker) walkOperation(op *spec.Operation) { + if op == nil { + return + } + s.walkParams(op.Parameters) + if op.Responses == nil { + return + } + s.walkResponse(op.Responses.Default) + for _, r := range op.Responses.StatusCodeResponses { + s.walkResponse(&r) + } +} + +func (s *readonlyReferenceWalker) Start() { + if s.root.Paths == nil { + return + } + for _, pathItem := range s.root.Paths.Paths { + s.walkParams(pathItem.Parameters) + s.walkOperation(pathItem.Delete) + s.walkOperation(pathItem.Get) + s.walkOperation(pathItem.Head) + s.walkOperation(pathItem.Options) + s.walkOperation(pathItem.Patch) + s.walkOperation(pathItem.Post) + s.walkOperation(pathItem.Put) + } +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 6c8d8f72e1ce..43f3a5f68779 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -839,6 +839,7 @@ k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/typed/apiextension k8s.io/apimachinery/pkg/api/equality k8s.io/apimachinery/pkg/api/errors k8s.io/apimachinery/pkg/api/meta +k8s.io/apimachinery/pkg/api/meta/table k8s.io/apimachinery/pkg/api/meta/testrestmapper k8s.io/apimachinery/pkg/api/resource k8s.io/apimachinery/pkg/api/validation @@ -1019,6 +1020,7 @@ k8s.io/apiserver/pkg/util/flowcontrol/metrics k8s.io/apiserver/pkg/util/flowcontrol/request k8s.io/apiserver/pkg/util/flushwriter k8s.io/apiserver/pkg/util/openapi +k8s.io/apiserver/pkg/util/proxy k8s.io/apiserver/pkg/util/shufflesharding k8s.io/apiserver/pkg/util/webhook k8s.io/apiserver/pkg/util/wsstream @@ -1429,16 +1431,37 @@ k8s.io/klog/v2/internal/severity # k8s.io/kube-aggregator v0.25.5 ## explicit; go 1.19 k8s.io/kube-aggregator/pkg/apis/apiregistration +k8s.io/kube-aggregator/pkg/apis/apiregistration/install k8s.io/kube-aggregator/pkg/apis/apiregistration/v1 k8s.io/kube-aggregator/pkg/apis/apiregistration/v1/helper k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1 +k8s.io/kube-aggregator/pkg/apis/apiregistration/validation +k8s.io/kube-aggregator/pkg/apiserver +k8s.io/kube-aggregator/pkg/apiserver/scheme k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/scheme k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1 k8s.io/kube-aggregator/pkg/client/clientset_generated/clientset/typed/apiregistration/v1beta1 +k8s.io/kube-aggregator/pkg/client/informers/externalversions +k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration +k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1 +k8s.io/kube-aggregator/pkg/client/informers/externalversions/apiregistration/v1beta1 +k8s.io/kube-aggregator/pkg/client/informers/externalversions/internalinterfaces +k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1 +k8s.io/kube-aggregator/pkg/client/listers/apiregistration/v1beta1 +k8s.io/kube-aggregator/pkg/controllers +k8s.io/kube-aggregator/pkg/controllers/openapi +k8s.io/kube-aggregator/pkg/controllers/openapi/aggregator +k8s.io/kube-aggregator/pkg/controllers/openapiv3 +k8s.io/kube-aggregator/pkg/controllers/openapiv3/aggregator +k8s.io/kube-aggregator/pkg/controllers/status +k8s.io/kube-aggregator/pkg/registry/apiservice +k8s.io/kube-aggregator/pkg/registry/apiservice/etcd +k8s.io/kube-aggregator/pkg/registry/apiservice/rest # k8s.io/kube-openapi v0.0.0-20220803162953-67bda5d908f1 ## explicit; go 1.18 k8s.io/kube-openapi/cmd/openapi-gen/args +k8s.io/kube-openapi/pkg/aggregator k8s.io/kube-openapi/pkg/builder k8s.io/kube-openapi/pkg/builder3 k8s.io/kube-openapi/pkg/builder3/util