add application controller (#2652)
Signed-off-by: Jeff <zw0948@gmail.com>
This commit is contained in:
215
vendor/sigs.k8s.io/controller-runtime/pkg/builder/controller.go
generated
vendored
Normal file
215
vendor/sigs.k8s.io/controller-runtime/pkg/builder/controller.go
generated
vendored
Normal file
@@ -0,0 +1,215 @@
|
||||
/*
|
||||
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 builder
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/controller"
|
||||
"sigs.k8s.io/controller-runtime/pkg/handler"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/predicate"
|
||||
"sigs.k8s.io/controller-runtime/pkg/reconcile"
|
||||
"sigs.k8s.io/controller-runtime/pkg/source"
|
||||
)
|
||||
|
||||
// Supporting mocking out functions for testing
|
||||
var newController = controller.New
|
||||
var getGvk = apiutil.GVKForObject
|
||||
|
||||
// Builder builds a Controller.
|
||||
type Builder struct {
|
||||
apiType runtime.Object
|
||||
mgr manager.Manager
|
||||
predicates []predicate.Predicate
|
||||
managedObjects []runtime.Object
|
||||
watchRequest []watchRequest
|
||||
config *rest.Config
|
||||
ctrl controller.Controller
|
||||
ctrlOptions controller.Options
|
||||
name string
|
||||
}
|
||||
|
||||
// ControllerManagedBy returns a new controller builder that will be started by the provided Manager
|
||||
func ControllerManagedBy(m manager.Manager) *Builder {
|
||||
return &Builder{mgr: m}
|
||||
}
|
||||
|
||||
// ForType defines the type of Object being *reconciled*, and configures the ControllerManagedBy to respond to create / delete /
|
||||
// update events by *reconciling the object*.
|
||||
// This is the equivalent of calling
|
||||
// Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
|
||||
//
|
||||
// Deprecated: Use For
|
||||
func (blder *Builder) ForType(apiType runtime.Object) *Builder {
|
||||
return blder.For(apiType)
|
||||
}
|
||||
|
||||
// For defines the type of Object being *reconciled*, and configures the ControllerManagedBy to respond to create / delete /
|
||||
// update events by *reconciling the object*.
|
||||
// This is the equivalent of calling
|
||||
// Watches(&source.Kind{Type: apiType}, &handler.EnqueueRequestForObject{})
|
||||
func (blder *Builder) For(apiType runtime.Object) *Builder {
|
||||
blder.apiType = apiType
|
||||
return blder
|
||||
}
|
||||
|
||||
// Owns defines types of Objects being *generated* by the ControllerManagedBy, and configures the ControllerManagedBy to respond to
|
||||
// create / delete / update events by *reconciling the owner object*. This is the equivalent of calling
|
||||
// Watches(&handler.EnqueueRequestForOwner{&source.Kind{Type: <ForType-apiType>}, &handler.EnqueueRequestForOwner{OwnerType: apiType, IsController: true})
|
||||
func (blder *Builder) Owns(apiType runtime.Object) *Builder {
|
||||
blder.managedObjects = append(blder.managedObjects, apiType)
|
||||
return blder
|
||||
}
|
||||
|
||||
type watchRequest struct {
|
||||
src source.Source
|
||||
eventhandler handler.EventHandler
|
||||
}
|
||||
|
||||
// Watches exposes the lower-level ControllerManagedBy Watches functions through the builder. Consider using
|
||||
// Owns or For instead of Watches directly.
|
||||
func (blder *Builder) Watches(src source.Source, eventhandler handler.EventHandler) *Builder {
|
||||
blder.watchRequest = append(blder.watchRequest, watchRequest{src: src, eventhandler: eventhandler})
|
||||
return blder
|
||||
}
|
||||
|
||||
// WithConfig sets the Config to use for configuring clients. Defaults to the in-cluster config or to ~/.kube/config.
|
||||
//
|
||||
// Deprecated: Use ControllerManagedBy(Manager) and this isn't needed.
|
||||
func (blder *Builder) WithConfig(config *rest.Config) *Builder {
|
||||
blder.config = config
|
||||
return blder
|
||||
}
|
||||
|
||||
// WithEventFilter sets the event filters, to filter which create/update/delete/generic events eventually
|
||||
// trigger reconciliations. For example, filtering on whether the resource version has changed.
|
||||
// Defaults to the empty list.
|
||||
func (blder *Builder) WithEventFilter(p predicate.Predicate) *Builder {
|
||||
blder.predicates = append(blder.predicates, p)
|
||||
return blder
|
||||
}
|
||||
|
||||
// WithOptions overrides the controller options use in doController. Defaults to empty.
|
||||
func (blder *Builder) WithOptions(options controller.Options) *Builder {
|
||||
blder.ctrlOptions = options
|
||||
return blder
|
||||
}
|
||||
|
||||
// Named sets the name of the controller to the given name. The name shows up
|
||||
// in metrics, among other things, and thus should be a prometheus compatible name
|
||||
// (underscores and alphanumeric characters only).
|
||||
//
|
||||
// By default, controllers are named using the lowercase version of their kind.
|
||||
func (blder *Builder) Named(name string) *Builder {
|
||||
blder.name = name
|
||||
return blder
|
||||
}
|
||||
|
||||
// Complete builds the Application ControllerManagedBy.
|
||||
func (blder *Builder) Complete(r reconcile.Reconciler) error {
|
||||
_, err := blder.Build(r)
|
||||
return err
|
||||
}
|
||||
|
||||
// Build builds the Application ControllerManagedBy and returns the Controller it created.
|
||||
func (blder *Builder) Build(r reconcile.Reconciler) (controller.Controller, error) {
|
||||
if r == nil {
|
||||
return nil, fmt.Errorf("must provide a non-nil Reconciler")
|
||||
}
|
||||
if blder.mgr == nil {
|
||||
return nil, fmt.Errorf("must provide a non-nil Manager")
|
||||
}
|
||||
|
||||
// Set the Config
|
||||
blder.loadRestConfig()
|
||||
|
||||
// Set the ControllerManagedBy
|
||||
if err := blder.doController(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Set the Watch
|
||||
if err := blder.doWatch(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blder.ctrl, nil
|
||||
}
|
||||
|
||||
func (blder *Builder) doWatch() error {
|
||||
// Reconcile type
|
||||
src := &source.Kind{Type: blder.apiType}
|
||||
hdler := &handler.EnqueueRequestForObject{}
|
||||
err := blder.ctrl.Watch(src, hdler, blder.predicates...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Watches the managed types
|
||||
for _, obj := range blder.managedObjects {
|
||||
src := &source.Kind{Type: obj}
|
||||
hdler := &handler.EnqueueRequestForOwner{
|
||||
OwnerType: blder.apiType,
|
||||
IsController: true,
|
||||
}
|
||||
if err := blder.ctrl.Watch(src, hdler, blder.predicates...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Do the watch requests
|
||||
for _, w := range blder.watchRequest {
|
||||
if err := blder.ctrl.Watch(w.src, w.eventhandler, blder.predicates...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (blder *Builder) loadRestConfig() {
|
||||
if blder.config == nil {
|
||||
blder.config = blder.mgr.GetConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func (blder *Builder) getControllerName() (string, error) {
|
||||
if blder.name != "" {
|
||||
return blder.name, nil
|
||||
}
|
||||
gvk, err := getGvk(blder.apiType, blder.mgr.GetScheme())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.ToLower(gvk.Kind), nil
|
||||
}
|
||||
|
||||
func (blder *Builder) doController(r reconcile.Reconciler) error {
|
||||
name, err := blder.getControllerName()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ctrlOptions := blder.ctrlOptions
|
||||
ctrlOptions.Reconciler = r
|
||||
blder.ctrl, err = newController(name, blder.mgr, ctrlOptions)
|
||||
return err
|
||||
}
|
||||
28
vendor/sigs.k8s.io/controller-runtime/pkg/builder/doc.go
generated
vendored
Normal file
28
vendor/sigs.k8s.io/controller-runtime/pkg/builder/doc.go
generated
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
/*
|
||||
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 builder provides wraps other controller-runtime libraries and exposes simple
|
||||
// patterns for building common Controllers.
|
||||
//
|
||||
// Projects built with the builder package can trivially be rebased on top of the underlying
|
||||
// packages if the project requires more customized behavior in the future.
|
||||
package builder
|
||||
|
||||
import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
|
||||
)
|
||||
|
||||
var log = logf.RuntimeLog.WithName("builder")
|
||||
166
vendor/sigs.k8s.io/controller-runtime/pkg/builder/webhook.go
generated
vendored
Normal file
166
vendor/sigs.k8s.io/controller-runtime/pkg/builder/webhook.go
generated
vendored
Normal file
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
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 builder
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
|
||||
"sigs.k8s.io/controller-runtime/pkg/manager"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
|
||||
"sigs.k8s.io/controller-runtime/pkg/webhook/conversion"
|
||||
)
|
||||
|
||||
// WebhookBuilder builds a Webhook.
|
||||
type WebhookBuilder struct {
|
||||
apiType runtime.Object
|
||||
gvk schema.GroupVersionKind
|
||||
mgr manager.Manager
|
||||
config *rest.Config
|
||||
}
|
||||
|
||||
func WebhookManagedBy(m manager.Manager) *WebhookBuilder {
|
||||
return &WebhookBuilder{mgr: m}
|
||||
}
|
||||
|
||||
// TODO(droot): update the GoDoc for conversion.
|
||||
|
||||
// For takes a runtime.Object which should be a CR.
|
||||
// If the given object implements the admission.Defaulter interface, a MutatingWebhook will be wired for this type.
|
||||
// If the given object implements the admission.Validator interface, a ValidatingWebhook will be wired for this type.
|
||||
func (blder *WebhookBuilder) For(apiType runtime.Object) *WebhookBuilder {
|
||||
blder.apiType = apiType
|
||||
return blder
|
||||
}
|
||||
|
||||
// Complete builds the webhook.
|
||||
func (blder *WebhookBuilder) Complete() error {
|
||||
// Set the Config
|
||||
blder.loadRestConfig()
|
||||
|
||||
// Set the Webhook if needed
|
||||
return blder.registerWebhooks()
|
||||
}
|
||||
|
||||
func (blder *WebhookBuilder) loadRestConfig() {
|
||||
if blder.config == nil {
|
||||
blder.config = blder.mgr.GetConfig()
|
||||
}
|
||||
}
|
||||
|
||||
func (blder *WebhookBuilder) registerWebhooks() error {
|
||||
// Create webhook(s) for each type
|
||||
var err error
|
||||
blder.gvk, err = apiutil.GVKForObject(blder.apiType, blder.mgr.GetScheme())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blder.registerDefaultingWebhook()
|
||||
blder.registerValidatingWebhook()
|
||||
|
||||
err = blder.registerConversionWebhook()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// registerDefaultingWebhook registers a defaulting webhook if th
|
||||
func (blder *WebhookBuilder) registerDefaultingWebhook() {
|
||||
defaulter, isDefaulter := blder.apiType.(admission.Defaulter)
|
||||
if !isDefaulter {
|
||||
log.Info("skip registering a mutating webhook, admission.Defaulter interface is not implemented", "GVK", blder.gvk)
|
||||
return
|
||||
}
|
||||
mwh := admission.DefaultingWebhookFor(defaulter)
|
||||
if mwh != nil {
|
||||
path := generateMutatePath(blder.gvk)
|
||||
|
||||
// Checking if the path is already registered.
|
||||
// If so, just skip it.
|
||||
if !blder.isAlreadyHandled(path) {
|
||||
log.Info("Registering a mutating webhook",
|
||||
"GVK", blder.gvk,
|
||||
"path", path)
|
||||
blder.mgr.GetWebhookServer().Register(path, mwh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (blder *WebhookBuilder) registerValidatingWebhook() {
|
||||
validator, isValidator := blder.apiType.(admission.Validator)
|
||||
if !isValidator {
|
||||
log.Info("skip registering a validating webhook, admission.Validator interface is not implemented", "GVK", blder.gvk)
|
||||
return
|
||||
}
|
||||
vwh := admission.ValidatingWebhookFor(validator)
|
||||
if vwh != nil {
|
||||
path := generateValidatePath(blder.gvk)
|
||||
|
||||
// Checking if the path is already registered.
|
||||
// If so, just skip it.
|
||||
if !blder.isAlreadyHandled(path) {
|
||||
log.Info("Registering a validating webhook",
|
||||
"GVK", blder.gvk,
|
||||
"path", path)
|
||||
blder.mgr.GetWebhookServer().Register(path, vwh)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (blder *WebhookBuilder) registerConversionWebhook() error {
|
||||
ok, err := conversion.IsConvertible(blder.mgr.GetScheme(), blder.apiType)
|
||||
if err != nil {
|
||||
log.Error(err, "conversion check failed", "object", blder.apiType)
|
||||
return err
|
||||
}
|
||||
if ok {
|
||||
if !blder.isAlreadyHandled("/convert") {
|
||||
blder.mgr.GetWebhookServer().Register("/convert", &conversion.Webhook{})
|
||||
}
|
||||
log.Info("conversion webhook enabled", "object", blder.apiType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (blder *WebhookBuilder) isAlreadyHandled(path string) bool {
|
||||
if blder.mgr.GetWebhookServer().WebhookMux == nil {
|
||||
return false
|
||||
}
|
||||
h, p := blder.mgr.GetWebhookServer().WebhookMux.Handler(&http.Request{URL: &url.URL{Path: path}})
|
||||
if p == path && h != nil {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func generateMutatePath(gvk schema.GroupVersionKind) string {
|
||||
return "/mutate-" + strings.Replace(gvk.Group, ".", "-", -1) + "-" +
|
||||
gvk.Version + "-" + strings.ToLower(gvk.Kind)
|
||||
}
|
||||
|
||||
func generateValidatePath(gvk schema.GroupVersionKind) string {
|
||||
return "/validate-" + strings.Replace(gvk.Group, ".", "-", -1) + "-" +
|
||||
gvk.Version + "-" + strings.ToLower(gvk.Kind)
|
||||
}
|
||||
40
vendor/sigs.k8s.io/controller-runtime/pkg/conversion/conversion.go
generated
vendored
Normal file
40
vendor/sigs.k8s.io/controller-runtime/pkg/conversion/conversion.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
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 conversion provides interface definitions that an API Type needs to
|
||||
implement for it to be supported by the generic conversion webhook handler
|
||||
defined under pkg/webhook/conversion.
|
||||
*/
|
||||
package conversion
|
||||
|
||||
import "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
// Convertible defines capability of a type to convertible i.e. it can be converted to/from a hub type.
|
||||
type Convertible interface {
|
||||
runtime.Object
|
||||
ConvertTo(dst Hub) error
|
||||
ConvertFrom(src Hub) error
|
||||
}
|
||||
|
||||
// Hub marks that a given type is the hub type for conversion. This means that
|
||||
// all conversions will first convert to the hub type, then convert from the hub
|
||||
// type to the destination type. All types besides the hub type should implement
|
||||
// Convertible.
|
||||
type Hub interface {
|
||||
runtime.Object
|
||||
Hub()
|
||||
}
|
||||
349
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/conversion/conversion.go
generated
vendored
Normal file
349
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/conversion/conversion.go
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
/*
|
||||
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 conversion provides implementation for CRD conversion webhook that implements handler for version conversion requests for types that are convertible.
|
||||
|
||||
See pkg/conversion for interface definitions required to ensure an API Type is convertible.
|
||||
*/
|
||||
package conversion
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
apix "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"sigs.k8s.io/controller-runtime/pkg/conversion"
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
)
|
||||
|
||||
var (
|
||||
log = logf.Log.WithName("conversion-webhook")
|
||||
)
|
||||
|
||||
// Webhook implements a CRD conversion webhook HTTP handler.
|
||||
type Webhook struct {
|
||||
scheme *runtime.Scheme
|
||||
decoder *Decoder
|
||||
}
|
||||
|
||||
// InjectScheme injects a scheme into the webhook, in order to construct a Decoder.
|
||||
func (wh *Webhook) InjectScheme(s *runtime.Scheme) error {
|
||||
var err error
|
||||
wh.scheme = s
|
||||
wh.decoder, err = NewDecoder(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensure Webhook implements http.Handler
|
||||
var _ http.Handler = &Webhook{}
|
||||
|
||||
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
convertReview := &apix.ConversionReview{}
|
||||
err := json.NewDecoder(r.Body).Decode(convertReview)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to read conversion request")
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(droot): may be move the conversion logic to a separate module to
|
||||
// decouple it from the http layer ?
|
||||
resp, err := wh.handleConvertRequest(convertReview.Request)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to convert", "request", convertReview.Request.UID)
|
||||
convertReview.Response = errored(err)
|
||||
} else {
|
||||
convertReview.Response = resp
|
||||
}
|
||||
convertReview.Response.UID = convertReview.Request.UID
|
||||
convertReview.Request = nil
|
||||
|
||||
err = json.NewEncoder(w).Encode(convertReview)
|
||||
if err != nil {
|
||||
log.Error(err, "failed to write response")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// handles a version conversion request.
|
||||
func (wh *Webhook) handleConvertRequest(req *apix.ConversionRequest) (*apix.ConversionResponse, error) {
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("conversion request is nil")
|
||||
}
|
||||
var objects []runtime.RawExtension
|
||||
|
||||
for _, obj := range req.Objects {
|
||||
src, gvk, err := wh.decoder.Decode(obj.Raw)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dst, err := wh.allocateDstObject(req.DesiredAPIVersion, gvk.Kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = wh.convertObject(src, dst)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
objects = append(objects, runtime.RawExtension{Object: dst})
|
||||
}
|
||||
return &apix.ConversionResponse{
|
||||
UID: req.UID,
|
||||
ConvertedObjects: objects,
|
||||
Result: metav1.Status{
|
||||
Status: metav1.StatusSuccess,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertObject will convert given a src object to dst object.
|
||||
// Note(droot): couldn't find a way to reduce the cyclomatic complexity under 10
|
||||
// without compromising readability, so disabling gocyclo linter
|
||||
func (wh *Webhook) convertObject(src, dst runtime.Object) error {
|
||||
srcGVK := src.GetObjectKind().GroupVersionKind()
|
||||
dstGVK := dst.GetObjectKind().GroupVersionKind()
|
||||
|
||||
if srcGVK.GroupKind() != dstGVK.GroupKind() {
|
||||
return fmt.Errorf("src %T and dst %T does not belong to same API Group", src, dst)
|
||||
}
|
||||
|
||||
if srcGVK == dstGVK {
|
||||
return fmt.Errorf("conversion is not allowed between same type %T", src)
|
||||
}
|
||||
|
||||
srcIsHub, dstIsHub := isHub(src), isHub(dst)
|
||||
srcIsConvertible, dstIsConvertible := isConvertible(src), isConvertible(dst)
|
||||
|
||||
switch {
|
||||
case srcIsHub && dstIsConvertible:
|
||||
return dst.(conversion.Convertible).ConvertFrom(src.(conversion.Hub))
|
||||
case dstIsHub && srcIsConvertible:
|
||||
return src.(conversion.Convertible).ConvertTo(dst.(conversion.Hub))
|
||||
case srcIsConvertible && dstIsConvertible:
|
||||
return wh.convertViaHub(src.(conversion.Convertible), dst.(conversion.Convertible))
|
||||
default:
|
||||
return fmt.Errorf("%T is not convertible to %T", src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (wh *Webhook) convertViaHub(src, dst conversion.Convertible) error {
|
||||
hub, err := wh.getHub(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if hub == nil {
|
||||
return fmt.Errorf("%s does not have any Hub defined", src)
|
||||
}
|
||||
|
||||
err = src.ConvertTo(hub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%T failed to convert to hub version %T : %v", src, hub, err)
|
||||
}
|
||||
|
||||
err = dst.ConvertFrom(hub)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%T failed to convert from hub version %T : %v", dst, hub, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getHub returns an instance of the Hub for passed-in object's group/kind.
|
||||
func (wh *Webhook) getHub(obj runtime.Object) (conversion.Hub, error) {
|
||||
gvks, err := objectGVKs(wh.scheme, obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(gvks) == 0 {
|
||||
return nil, fmt.Errorf("error retrieving gvks for object : %v", obj)
|
||||
}
|
||||
|
||||
var hub conversion.Hub
|
||||
var hubFoundAlready bool
|
||||
for _, gvk := range gvks {
|
||||
instance, err := wh.scheme.New(gvk)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err)
|
||||
}
|
||||
if val, isHub := instance.(conversion.Hub); isHub {
|
||||
if hubFoundAlready {
|
||||
return nil, fmt.Errorf("multiple hub version defined for %T", obj)
|
||||
}
|
||||
hubFoundAlready = true
|
||||
hub = val
|
||||
}
|
||||
}
|
||||
return hub, nil
|
||||
}
|
||||
|
||||
// allocateDstObject returns an instance for a given GVK.
|
||||
func (wh *Webhook) allocateDstObject(apiVersion, kind string) (runtime.Object, error) {
|
||||
gvk := schema.FromAPIVersionAndKind(apiVersion, kind)
|
||||
|
||||
obj, err := wh.scheme.New(gvk)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
|
||||
t, err := meta.TypeAccessor(obj)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
|
||||
t.SetAPIVersion(apiVersion)
|
||||
t.SetKind(kind)
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// IsConvertible determines if given type is convertible or not. For a type
|
||||
// to be convertible, the group-kind needs to have a Hub type defined and all
|
||||
// non-hub types must be able to convert to/from Hub.
|
||||
func IsConvertible(scheme *runtime.Scheme, obj runtime.Object) (bool, error) {
|
||||
var hubs, spokes, nonSpokes []runtime.Object
|
||||
|
||||
gvks, err := objectGVKs(scheme, obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if len(gvks) == 0 {
|
||||
return false, fmt.Errorf("error retrieving gvks for object : %v", obj)
|
||||
}
|
||||
|
||||
for _, gvk := range gvks {
|
||||
instance, err := scheme.New(gvk)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("failed to allocate an instance for gvk %v %v", gvk, err)
|
||||
}
|
||||
|
||||
if isHub(instance) {
|
||||
hubs = append(hubs, instance)
|
||||
continue
|
||||
}
|
||||
|
||||
if !isConvertible(instance) {
|
||||
nonSpokes = append(nonSpokes, instance)
|
||||
continue
|
||||
}
|
||||
|
||||
spokes = append(spokes, instance)
|
||||
}
|
||||
|
||||
if len(gvks) == 1 {
|
||||
return false, nil // single version
|
||||
}
|
||||
|
||||
if len(hubs) == 0 && len(spokes) == 0 {
|
||||
// multiple version detected with no conversion implementation. This is
|
||||
// true for multi-version built-in types.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(hubs) == 1 && len(nonSpokes) == 0 { // convertible
|
||||
spokeVersions := []string{}
|
||||
for _, sp := range spokes {
|
||||
spokeVersions = append(spokeVersions, sp.GetObjectKind().GroupVersionKind().String())
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, PartialImplementationError{
|
||||
hubs: hubs,
|
||||
nonSpokes: nonSpokes,
|
||||
spokes: spokes,
|
||||
}
|
||||
}
|
||||
|
||||
// objectGVKs returns all (Group,Version,Kind) for the Group/Kind of given object.
|
||||
func objectGVKs(scheme *runtime.Scheme, obj runtime.Object) ([]schema.GroupVersionKind, error) {
|
||||
// NB: we should not use `obj.GetObjectKind().GroupVersionKind()` to get the
|
||||
// GVK here, since it is parsed from apiVersion and kind fields and it may
|
||||
// return empty GVK if obj is an uninitialized object.
|
||||
objGVKs, _, err := scheme.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(objGVKs) != 1 {
|
||||
return nil, fmt.Errorf("expect to get only one GVK for %v", obj)
|
||||
}
|
||||
objGVK := objGVKs[0]
|
||||
knownTypes := scheme.AllKnownTypes()
|
||||
|
||||
var gvks []schema.GroupVersionKind
|
||||
for gvk := range knownTypes {
|
||||
if objGVK.GroupKind() == gvk.GroupKind() {
|
||||
gvks = append(gvks, gvk)
|
||||
}
|
||||
}
|
||||
return gvks, nil
|
||||
}
|
||||
|
||||
// PartialImplementationError represents an error due to partial conversion
|
||||
// implementation such as hub without spokes, multiple hubs or spokes without hub.
|
||||
type PartialImplementationError struct {
|
||||
gvk schema.GroupVersionKind
|
||||
hubs []runtime.Object
|
||||
nonSpokes []runtime.Object
|
||||
spokes []runtime.Object
|
||||
}
|
||||
|
||||
func (e PartialImplementationError) Error() string {
|
||||
if len(e.hubs) == 0 {
|
||||
return fmt.Sprintf("no hub defined for gvk %s", e.gvk)
|
||||
}
|
||||
if len(e.hubs) > 1 {
|
||||
return fmt.Sprintf("multiple(%d) hubs defined for group-kind '%s' ",
|
||||
len(e.hubs), e.gvk.GroupKind())
|
||||
}
|
||||
if len(e.nonSpokes) > 0 {
|
||||
return fmt.Sprintf("%d inconvertible types detected for group-kind '%s'",
|
||||
len(e.nonSpokes), e.gvk.GroupKind())
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// isHub determines if passed-in object is a Hub or not.
|
||||
func isHub(obj runtime.Object) bool {
|
||||
_, yes := obj.(conversion.Hub)
|
||||
return yes
|
||||
}
|
||||
|
||||
// isConvertible determines if passed-in object is a convertible.
|
||||
func isConvertible(obj runtime.Object) bool {
|
||||
_, yes := obj.(conversion.Convertible)
|
||||
return yes
|
||||
}
|
||||
|
||||
// helper to construct error response.
|
||||
func errored(err error) *apix.ConversionResponse {
|
||||
return &apix.ConversionResponse{
|
||||
Result: metav1.Status{
|
||||
Status: metav1.StatusFailure,
|
||||
Message: err.Error(),
|
||||
},
|
||||
}
|
||||
}
|
||||
31
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/conversion/decoder.go
generated
vendored
Normal file
31
vendor/sigs.k8s.io/controller-runtime/pkg/webhook/conversion/decoder.go
generated
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package conversion
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
)
|
||||
|
||||
// Decoder knows how to decode the contents of a CRD version conversion
|
||||
// request into a concrete object.
|
||||
// TODO(droot): consider reusing decoder from admission pkg for this.
|
||||
type Decoder struct {
|
||||
codecs serializer.CodecFactory
|
||||
}
|
||||
|
||||
// NewDecoder creates a Decoder given the runtime.Scheme
|
||||
func NewDecoder(scheme *runtime.Scheme) (*Decoder, error) {
|
||||
return &Decoder{codecs: serializer.NewCodecFactory(scheme)}, nil
|
||||
}
|
||||
|
||||
// Decode decodes the inlined object.
|
||||
func (d *Decoder) Decode(content []byte) (runtime.Object, *schema.GroupVersionKind, error) {
|
||||
deserializer := d.codecs.UniversalDeserializer()
|
||||
return deserializer.Decode(content, nil, nil)
|
||||
}
|
||||
|
||||
// DecodeInto decodes the inlined object in the into the passed-in runtime.Object.
|
||||
func (d *Decoder) DecodeInto(content []byte, into runtime.Object) error {
|
||||
deserializer := d.codecs.UniversalDeserializer()
|
||||
return runtime.DecodeInto(deserializer, content, into)
|
||||
}
|
||||
Reference in New Issue
Block a user