From 4395c598b78c4c3aaa6ae3adef66bc3234fea00b Mon Sep 17 00:00:00 2001 From: Thomas Shafer Date: Tue, 17 Mar 2020 13:33:52 -0700 Subject: [PATCH 1/2] Generate reconciler for knativeserving --- Gopkg.lock | 13 + .../v1alpha1/knativeserving_lifecycle.go | 10 - .../serving/v1alpha1/knativeserving_types.go | 9 +- .../serving/v1alpha1/zz_generated.deepcopy.go | 9 +- .../v1alpha1/knativeserving/controller.go | 97 +++++ .../v1alpha1/knativeserving/reconciler.go | 339 ++++++++++++++++++ .../knativeserving/stub/controller.go | 54 +++ .../knativeserving/stub/reconciler.go | 66 ++++ third_party/VENDOR-LICENSE | 208 +++++++++++ vendor/gomodules.xyz/jsonpatch/LICENSE | 202 +++++++++++ .../gomodules.xyz/jsonpatch/v2/jsonpatch.go | 336 +++++++++++++++++ 11 files changed, 1320 insertions(+), 23 deletions(-) create mode 100644 pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/controller.go create mode 100644 pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/reconciler.go create mode 100644 pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/controller.go create mode 100644 pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/reconciler.go create mode 100644 vendor/gomodules.xyz/jsonpatch/LICENSE create mode 100644 vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go diff --git a/Gopkg.lock b/Gopkg.lock index 1c11d7c1..c6126012 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -710,6 +710,14 @@ pruneopts = "NUT" revision = "593de606220b6283bd14765aab1e244ea650cd84" +[[projects]] + digest = "1:79c5b7c79609e7e2d0c5a6509cc5894bb8ee9c2ca6e741214f8aa4317beeea59" + name = "gomodules.xyz/jsonpatch" + packages = ["v2"] + pruneopts = "NUT" + revision = "1c2a0262323edc27502942d9727c95646b1c3cbe" + version = "v2.1.0" + [[projects]] digest = "1:658567ee8cf9149bcb84fbc019f4577be73aae97903e9bdcffc9a0abfa339ef5" name = "google.golang.org/api" @@ -1326,6 +1334,8 @@ name = "knative.dev/pkg" packages = [ "apis", + "apis/duck", + "apis/duck/v1", "changeset", "client/injection/kube/client", "client/injection/kube/client/fake", @@ -1362,6 +1372,7 @@ "test/monitoring", "test/spoof", "test/zipkin", + "tracker", "version", "webhook", "webhook/certificates/resources", @@ -1440,6 +1451,7 @@ "k8s.io/kube-openapi/pkg/common", "knative.dev/caching/pkg/apis/caching/v1alpha1", "knative.dev/pkg/apis", + "knative.dev/pkg/apis/duck/v1", "knative.dev/pkg/client/injection/kube/client", "knative.dev/pkg/client/injection/kube/client/fake", "knative.dev/pkg/client/injection/kube/informers/apps/v1/deployment", @@ -1454,6 +1466,7 @@ "knative.dev/pkg/logging/logkey", "knative.dev/pkg/metrics", "knative.dev/pkg/metrics/metricstest", + "knative.dev/pkg/reconciler", "knative.dev/pkg/signals", "knative.dev/pkg/test", "knative.dev/pkg/test/logging", diff --git a/pkg/apis/serving/v1alpha1/knativeserving_lifecycle.go b/pkg/apis/serving/v1alpha1/knativeserving_lifecycle.go index 788ee718..b6413045 100644 --- a/pkg/apis/serving/v1alpha1/knativeserving_lifecycle.go +++ b/pkg/apis/serving/v1alpha1/knativeserving_lifecycle.go @@ -31,16 +31,6 @@ func (ks *KnativeServing) GroupVersionKind() schema.GroupVersionKind { return SchemeGroupVersion.WithKind(Kind) } -// GetConditions implements apis.ConditionsAccessor -func (is *KnativeServingStatus) GetConditions() apis.Conditions { - return is.Conditions -} - -// SetConditions implements apis.ConditionsAccessor -func (is *KnativeServingStatus) SetConditions(c apis.Conditions) { - is.Conditions = c -} - func (is *KnativeServingStatus) IsReady() bool { return conditions.Manage(is).IsHappy() } diff --git a/pkg/apis/serving/v1alpha1/knativeserving_types.go b/pkg/apis/serving/v1alpha1/knativeserving_types.go index 54da4277..7ecdedcb 100644 --- a/pkg/apis/serving/v1alpha1/knativeserving_types.go +++ b/pkg/apis/serving/v1alpha1/knativeserving_types.go @@ -19,6 +19,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "knative.dev/pkg/apis" + duckv1 "knative.dev/pkg/apis/duck/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -123,6 +124,8 @@ type KnativeServingSpec struct { // KnativeServingStatus defines the observed state of KnativeServing // +k8s:openapi-gen=true type KnativeServingStatus struct { + duckv1.Status `json:",inline"` + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file // Add custom validation using kubebuilder tags: @@ -131,14 +134,10 @@ type KnativeServingStatus struct { // The version of the installed release // +optional Version string `json:"version,omitempty"` - // The latest available observations of a resource's current state. - // +optional - // +patchMergeKey=type - // +patchStrategy=merge - Conditions apis.Conditions `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type"` } // +genclient +// +genreconciler // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object // KnativeServing is the Schema for the knativeservings API diff --git a/pkg/apis/serving/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/serving/v1alpha1/zz_generated.deepcopy.go index 197ef27d..a8ae290a 100644 --- a/pkg/apis/serving/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/serving/v1alpha1/zz_generated.deepcopy.go @@ -23,7 +23,6 @@ package v1alpha1 import ( v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" - apis "knative.dev/pkg/apis" ) // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. @@ -194,13 +193,7 @@ func (in *KnativeServingSpec) DeepCopy() *KnativeServingSpec { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *KnativeServingStatus) DeepCopyInto(out *KnativeServingStatus) { *out = *in - if in.Conditions != nil { - in, out := &in.Conditions, &out.Conditions - *out = make(apis.Conditions, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } + in.Status.DeepCopyInto(&out.Status) return } diff --git a/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/controller.go b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/controller.go new file mode 100644 index 00000000..871b1e2c --- /dev/null +++ b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/controller.go @@ -0,0 +1,97 @@ +/* +Copyright 2020 The Knative 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 injection-gen. DO NOT EDIT. + +package knativeserving + +import ( + context "context" + + corev1 "k8s.io/api/core/v1" + watch "k8s.io/apimachinery/pkg/watch" + scheme "k8s.io/client-go/kubernetes/scheme" + v1 "k8s.io/client-go/kubernetes/typed/core/v1" + record "k8s.io/client-go/tools/record" + client "knative.dev/pkg/client/injection/kube/client" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + versionedscheme "knative.dev/serving-operator/pkg/client/clientset/versioned/scheme" + injectionclient "knative.dev/serving-operator/pkg/client/injection/client" + knativeserving "knative.dev/serving-operator/pkg/client/injection/informers/serving/v1alpha1/knativeserving" +) + +const ( + defaultControllerAgentName = "knativeserving-controller" + defaultFinalizerName = "knativeservings.operator.knative.dev" + defaultQueueName = "knativeservings" +) + +// NewImpl returns a controller.Impl that handles queuing and feeding work from +// the queue through an implementation of controller.Reconciler, delegating to +// the provided Interface and optional Finalizer methods. OptionsFn is used to return +// controller.Options to be used but the internal reconciler. +func NewImpl(ctx context.Context, r Interface, optionsFns ...controller.OptionsFn) *controller.Impl { + logger := logging.FromContext(ctx) + + // Check the options function input. It should be 0 or 1. + if len(optionsFns) > 1 { + logger.Fatalf("up to one options function is supported, found %d", len(optionsFns)) + } + + knativeservingInformer := knativeserving.Get(ctx) + + recorder := controller.GetEventRecorder(ctx) + if recorder == nil { + // Create event broadcaster + logger.Debug("Creating event broadcaster") + eventBroadcaster := record.NewBroadcaster() + watches := []watch.Interface{ + eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), + eventBroadcaster.StartRecordingToSink( + &v1.EventSinkImpl{Interface: client.Get(ctx).CoreV1().Events("")}), + } + recorder = eventBroadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: defaultControllerAgentName}) + go func() { + <-ctx.Done() + for _, w := range watches { + w.Stop() + } + }() + } + + rec := &reconcilerImpl{ + Client: injectionclient.Get(ctx), + Lister: knativeservingInformer.Lister(), + Recorder: recorder, + reconciler: r, + } + impl := controller.NewImpl(rec, logger, defaultQueueName) + + // Pass impl to the options. Save any optional results. + for _, fn := range optionsFns { + opts := fn(impl) + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + } + + return impl +} + +func init() { + versionedscheme.AddToScheme(scheme.Scheme) +} diff --git a/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/reconciler.go b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/reconciler.go new file mode 100644 index 00000000..3ab35031 --- /dev/null +++ b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/reconciler.go @@ -0,0 +1,339 @@ +/* +Copyright 2020 The Knative 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 injection-gen. DO NOT EDIT. + +package knativeserving + +import ( + context "context" + "encoding/json" + "reflect" + + zap "go.uber.org/zap" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/equality" + errors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + sets "k8s.io/apimachinery/pkg/util/sets" + cache "k8s.io/client-go/tools/cache" + record "k8s.io/client-go/tools/record" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + reconciler "knative.dev/pkg/reconciler" + v1alpha1 "knative.dev/serving-operator/pkg/apis/serving/v1alpha1" + versioned "knative.dev/serving-operator/pkg/client/clientset/versioned" + servingv1alpha1 "knative.dev/serving-operator/pkg/client/listers/serving/v1alpha1" +) + +// Interface defines the strongly typed interfaces to be implemented by a +// controller reconciling v1alpha1.KnativeServing. +type Interface interface { + // ReconcileKind implements custom logic to reconcile v1alpha1.KnativeServing. Any changes + // to the objects .Status or .Finalizers will be propagated to the stored + // object. It is recommended that implementors do not call any update calls + // for the Kind inside of ReconcileKind, it is the responsibility of the calling + // controller to propagate those properties. The resource passed to ReconcileKind + // will always have an empty deletion timestamp. + ReconcileKind(ctx context.Context, o *v1alpha1.KnativeServing) reconciler.Event +} + +// Finalizer defines the strongly typed interfaces to be implemented by a +// controller finalizing v1alpha1.KnativeServing. +type Finalizer interface { + // FinalizeKind implements custom logic to finalize v1alpha1.KnativeServing. Any changes + // to the objects .Status or .Finalizers will be ignored. Returning a nil or + // Normal type reconciler.Event will allow the finalizer to be deleted on + // the resource. The resource passed to FinalizeKind will always have a set + // deletion timestamp. + FinalizeKind(ctx context.Context, o *v1alpha1.KnativeServing) reconciler.Event +} + +// reconcilerImpl implements controller.Reconciler for v1alpha1.KnativeServing resources. +type reconcilerImpl struct { + // Client is used to write back status updates. + Client versioned.Interface + + // Listers index properties about resources + Lister servingv1alpha1.KnativeServingLister + + // Recorder is an event recorder for recording Event resources to the + // Kubernetes API. + Recorder record.EventRecorder + + // configStore allows for decorating a context with config maps. + // +optional + configStore reconciler.ConfigStore + + // reconciler is the implementation of the business logic of the resource. + reconciler Interface +} + +// Check that our Reconciler implements controller.Reconciler +var _ controller.Reconciler = (*reconcilerImpl)(nil) + +func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client versioned.Interface, lister servingv1alpha1.KnativeServingLister, recorder record.EventRecorder, r Interface, options ...controller.Options) controller.Reconciler { + // Check the options function input. It should be 0 or 1. + if len(options) > 1 { + logger.Fatalf("up to one options struct is supported, found %d", len(options)) + } + + rec := &reconcilerImpl{ + Client: client, + Lister: lister, + Recorder: recorder, + reconciler: r, + } + + for _, opts := range options { + if opts.ConfigStore != nil { + rec.configStore = opts.ConfigStore + } + } + + return rec +} + +// Reconcile implements controller.Reconciler +func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error { + logger := logging.FromContext(ctx) + + // If configStore is set, attach the frozen configuration to the context. + if r.configStore != nil { + ctx = r.configStore.ToContext(ctx) + } + + // Add the recorder to context. + ctx = controller.WithEventRecorder(ctx, r.Recorder) + + // Convert the namespace/name string into a distinct namespace and name + + namespace, name, err := cache.SplitMetaNamespaceKey(key) + + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil + } + + // Get the resource with this namespace/name. + + getter := r.Lister.KnativeServings(namespace) + + original, err := getter.Get(name) + + if errors.IsNotFound(err) { + // The resource may no longer exist, in which case we stop processing. + logger.Errorf("resource %q no longer exists", key) + return nil + } else if err != nil { + return err + } + + // Don't modify the informers copy. + resource := original.DeepCopy() + + var reconcileEvent reconciler.Event + if resource.GetDeletionTimestamp().IsZero() { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "ReconcileKind")) + + // Set and update the finalizer on resource if r.reconciler + // implements Finalizer. + if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil { + logger.Warnw("Failed to set finalizers", zap.Error(err)) + } + + // Reconcile this copy of the resource and then write back any status + // updates regardless of whether the reconciliation errored out. + reconcileEvent = r.reconciler.ReconcileKind(ctx, resource) + } else if fin, ok := r.reconciler.(Finalizer); ok { + // Append the target method to the logger. + logger = logger.With(zap.String("targetMethod", "FinalizeKind")) + + // For finalizing reconcilers, if this resource being marked for deletion + // and reconciled cleanly (nil or normal event), remove the finalizer. + reconcileEvent = fin.FinalizeKind(ctx, resource) + if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil { + logger.Warnw("Failed to clear finalizers", zap.Error(err)) + } + } + + // Synchronize the status. + if equality.Semantic.DeepEqual(original.Status, resource.Status) { + // If we didn't change anything then don't call updateStatus. + // This is important because the copy we loaded from the injectionInformer's + // cache may be stale and we don't want to overwrite a prior update + // to status with this stale state. + } else if err = r.updateStatus(original, resource); err != nil { + logger.Warnw("Failed to update resource status", zap.Error(err)) + r.Recorder.Eventf(resource, v1.EventTypeWarning, "UpdateFailed", + "Failed to update status for %q: %v", resource.Name, err) + return err + } + + // Report the reconciler event, if any. + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + logger.Infow("returned an event", zap.Any("event", reconcileEvent)) + r.Recorder.Eventf(resource, event.EventType, event.Reason, event.Format, event.Args...) + return nil + } else { + logger.Errorw("returned an error", zap.Error(reconcileEvent)) + r.Recorder.Event(resource, v1.EventTypeWarning, "InternalError", reconcileEvent.Error()) + return reconcileEvent + } + } + return nil +} + +func (r *reconcilerImpl) updateStatus(existing *v1alpha1.KnativeServing, desired *v1alpha1.KnativeServing) error { + existing = existing.DeepCopy() + return reconciler.RetryUpdateConflicts(func(attempts int) (err error) { + // The first iteration tries to use the injectionInformer's state, subsequent attempts fetch the latest state via API. + if attempts > 0 { + + getter := r.Client.OperatorV1alpha1().KnativeServings(desired.Namespace) + + existing, err = getter.Get(desired.Name, metav1.GetOptions{}) + if err != nil { + return err + } + } + + // If there's nothing to update, just return. + if reflect.DeepEqual(existing.Status, desired.Status) { + return nil + } + + existing.Status = desired.Status + + updater := r.Client.OperatorV1alpha1().KnativeServings(existing.Namespace) + + _, err = updater.UpdateStatus(existing) + return err + }) +} + +// updateFinalizersFiltered will update the Finalizers of the resource. +// TODO: this method could be generic and sync all finalizers. For now it only +// updates defaultFinalizerName. +func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1alpha1.KnativeServing) (*v1alpha1.KnativeServing, error) { + finalizerName := defaultFinalizerName + + getter := r.Lister.KnativeServings(resource.Namespace) + + actual, err := getter.Get(resource.Name) + if err != nil { + return resource, err + } + + // Don't modify the informers copy. + existing := actual.DeepCopy() + + var finalizers []string + + // If there's nothing to update, just return. + existingFinalizers := sets.NewString(existing.Finalizers...) + desiredFinalizers := sets.NewString(resource.Finalizers...) + + if desiredFinalizers.Has(finalizerName) { + if existingFinalizers.Has(finalizerName) { + // Nothing to do. + return resource, nil + } + // Add the finalizer. + finalizers = append(existing.Finalizers, finalizerName) + } else { + if !existingFinalizers.Has(finalizerName) { + // Nothing to do. + return resource, nil + } + // Remove the finalizer. + existingFinalizers.Delete(finalizerName) + finalizers = existingFinalizers.List() + } + + mergePatch := map[string]interface{}{ + "metadata": map[string]interface{}{ + "finalizers": finalizers, + "resourceVersion": existing.ResourceVersion, + }, + } + + patch, err := json.Marshal(mergePatch) + if err != nil { + return resource, err + } + + patcher := r.Client.OperatorV1alpha1().KnativeServings(resource.Namespace) + + resource, err = patcher.Patch(resource.Name, types.MergePatchType, patch) + if err != nil { + r.Recorder.Eventf(resource, v1.EventTypeWarning, "FinalizerUpdateFailed", + "Failed to update finalizers for %q: %v", resource.Name, err) + } else { + r.Recorder.Eventf(resource, v1.EventTypeNormal, "FinalizerUpdate", + "Updated %q finalizers", resource.GetName()) + } + return resource, err +} + +func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1alpha1.KnativeServing) (*v1alpha1.KnativeServing, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + // If this resource is not being deleted, mark the finalizer. + if resource.GetDeletionTimestamp().IsZero() { + finalizers.Insert(defaultFinalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by defaultFinalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} + +func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1alpha1.KnativeServing, reconcileEvent reconciler.Event) (*v1alpha1.KnativeServing, error) { + if _, ok := r.reconciler.(Finalizer); !ok { + return resource, nil + } + if resource.GetDeletionTimestamp().IsZero() { + return resource, nil + } + + finalizers := sets.NewString(resource.Finalizers...) + + if reconcileEvent != nil { + var event *reconciler.ReconcilerEvent + if reconciler.EventAs(reconcileEvent, &event) { + if event.EventType == v1.EventTypeNormal { + finalizers.Delete(defaultFinalizerName) + } + } + } else { + finalizers.Delete(defaultFinalizerName) + } + + resource.Finalizers = finalizers.List() + + // Synchronize the finalizers filtered by defaultFinalizerName. + return r.updateFinalizersFiltered(ctx, resource) +} diff --git a/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/controller.go b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/controller.go new file mode 100644 index 00000000..9a0c1b06 --- /dev/null +++ b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/controller.go @@ -0,0 +1,54 @@ +/* +Copyright 2020 The Knative 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 injection-gen. DO NOT EDIT. + +package knativeserving + +import ( + context "context" + + configmap "knative.dev/pkg/configmap" + controller "knative.dev/pkg/controller" + logging "knative.dev/pkg/logging" + knativeserving "knative.dev/serving-operator/pkg/client/injection/informers/serving/v1alpha1/knativeserving" + v1alpha1knativeserving "knative.dev/serving-operator/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving" +) + +// TODO: PLEASE COPY AND MODIFY THIS FILE AS A STARTING POINT + +// NewController creates a Reconciler for KnativeServing and returns the result of NewImpl. +func NewController( + ctx context.Context, + cmw configmap.Watcher, +) *controller.Impl { + logger := logging.FromContext(ctx) + + knativeservingInformer := knativeserving.Get(ctx) + + // TODO: setup additional informers here. + + r := &Reconciler{} + impl := v1alpha1knativeserving.NewImpl(ctx, r) + + logger.Info("Setting up event handlers.") + + knativeservingInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) + + // TODO: add additional informer event handlers here. + + return impl +} diff --git a/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/reconciler.go b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/reconciler.go new file mode 100644 index 00000000..3e8270b7 --- /dev/null +++ b/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving/stub/reconciler.go @@ -0,0 +1,66 @@ +/* +Copyright 2020 The Knative 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 injection-gen. DO NOT EDIT. + +package knativeserving + +import ( + context "context" + + v1 "k8s.io/api/core/v1" + reconciler "knative.dev/pkg/reconciler" + v1alpha1 "knative.dev/serving-operator/pkg/apis/serving/v1alpha1" + knativeserving "knative.dev/serving-operator/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving" +) + +// TODO: PLEASE COPY AND MODIFY THIS FILE AS A STARTING POINT + +// newReconciledNormal makes a new reconciler event with event type Normal, and +// reason KnativeServingReconciled. +func newReconciledNormal(namespace, name string) reconciler.Event { + return reconciler.NewEvent(v1.EventTypeNormal, "KnativeServingReconciled", "KnativeServing reconciled: \"%s/%s\"", namespace, name) +} + +// Reconciler implements controller.Reconciler for KnativeServing resources. +type Reconciler struct { + // TODO: add additional requirements here. +} + +// Check that our Reconciler implements Interface +var _ knativeserving.Interface = (*Reconciler)(nil) + +// Optionally check that our Reconciler implements Finalizer +//var _ knativeserving.Finalizer = (*Reconciler)(nil) + +// ReconcileKind implements Interface.ReconcileKind. +func (r *Reconciler) ReconcileKind(ctx context.Context, o *v1alpha1.KnativeServing) reconciler.Event { + // TODO: use this if the resource implements InitializeConditions. + // o.Status.InitializeConditions() + + // TODO: add custom reconciliation logic here. + + // TODO: use this if the object has .status.ObservedGeneration. + // o.Status.ObservedGeneration = o.Generation + return newReconciledNormal(o.Namespace, o.Name) +} + +// Optionally, use FinalizeKind to add finalizers. FinalizeKind will be called +// when the resource is deleted. +//func (r *Reconciler) FinalizeKind(ctx context.Context, o *v1alpha1.KnativeServing) reconciler.Event { +// // TODO: add custom finalization logic here. +// return nil +//} diff --git a/third_party/VENDOR-LICENSE b/third_party/VENDOR-LICENSE index 96b6746e..c85ef48a 100644 --- a/third_party/VENDOR-LICENSE +++ b/third_party/VENDOR-LICENSE @@ -6603,6 +6603,214 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +=========================================================== +Import: knative.dev/serving-operator/vendor/gomodules.xyz/jsonpatch + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + + + + =========================================================== Import: knative.dev/serving-operator/vendor/google.golang.org/api diff --git a/vendor/gomodules.xyz/jsonpatch/LICENSE b/vendor/gomodules.xyz/jsonpatch/LICENSE new file mode 100644 index 00000000..8f71f43f --- /dev/null +++ b/vendor/gomodules.xyz/jsonpatch/LICENSE @@ -0,0 +1,202 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. + diff --git a/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go b/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go new file mode 100644 index 00000000..b8ae4456 --- /dev/null +++ b/vendor/gomodules.xyz/jsonpatch/v2/jsonpatch.go @@ -0,0 +1,336 @@ +package jsonpatch + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strings" +) + +var errBadJSONDoc = fmt.Errorf("invalid JSON Document") + +type JsonPatchOperation = Operation + +type Operation struct { + Operation string `json:"op"` + Path string `json:"path"` + Value interface{} `json:"value,omitempty"` +} + +func (j *Operation) Json() string { + b, _ := json.Marshal(j) + return string(b) +} + +func (j *Operation) MarshalJSON() ([]byte, error) { + var b bytes.Buffer + b.WriteString("{") + b.WriteString(fmt.Sprintf(`"op":"%s"`, j.Operation)) + b.WriteString(fmt.Sprintf(`,"path":"%s"`, j.Path)) + // Consider omitting Value for non-nullable operations. + if j.Value != nil || j.Operation == "replace" || j.Operation == "add" { + v, err := json.Marshal(j.Value) + if err != nil { + return nil, err + } + b.WriteString(`,"value":`) + b.Write(v) + } + b.WriteString("}") + return b.Bytes(), nil +} + +type ByPath []Operation + +func (a ByPath) Len() int { return len(a) } +func (a ByPath) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByPath) Less(i, j int) bool { return a[i].Path < a[j].Path } + +func NewOperation(op, path string, value interface{}) Operation { + return Operation{Operation: op, Path: path, Value: value} +} + +// CreatePatch creates a patch as specified in http://jsonpatch.com/ +// +// 'a' is original, 'b' is the modified document. Both are to be given as json encoded content. +// The function will return an array of JsonPatchOperations +// +// An error will be returned if any of the two documents are invalid. +func CreatePatch(a, b []byte) ([]Operation, error) { + var aI interface{} + var bI interface{} + err := json.Unmarshal(a, &aI) + if err != nil { + return nil, errBadJSONDoc + } + err = json.Unmarshal(b, &bI) + if err != nil { + return nil, errBadJSONDoc + } + return handleValues(aI, bI, "", []Operation{}) +} + +// Returns true if the values matches (must be json types) +// The types of the values must match, otherwise it will always return false +// If two map[string]interface{} are given, all elements must match. +func matchesValue(av, bv interface{}) bool { + if reflect.TypeOf(av) != reflect.TypeOf(bv) { + return false + } + switch at := av.(type) { + case string: + bt, ok := bv.(string) + if ok && bt == at { + return true + } + case float64: + bt, ok := bv.(float64) + if ok && bt == at { + return true + } + case bool: + bt, ok := bv.(bool) + if ok && bt == at { + return true + } + case map[string]interface{}: + bt, ok := bv.(map[string]interface{}) + if !ok { + return false + } + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + case []interface{}: + bt, ok := bv.([]interface{}) + if !ok { + return false + } + if len(bt) != len(at) { + return false + } + for key := range at { + if !matchesValue(at[key], bt[key]) { + return false + } + } + for key := range bt { + if !matchesValue(at[key], bt[key]) { + return false + } + } + return true + } + return false +} + +// From http://tools.ietf.org/html/rfc6901#section-4 : +// +// Evaluation of each reference token begins by decoding any escaped +// character sequence. This is performed by first transforming any +// occurrence of the sequence '~1' to '/', and then transforming any +// occurrence of the sequence '~0' to '~'. +// TODO decode support: +// var rfc6901Decoder = strings.NewReplacer("~1", "/", "~0", "~") + +var rfc6901Encoder = strings.NewReplacer("~", "~0", "/", "~1") + +func makePath(path string, newPart interface{}) string { + key := rfc6901Encoder.Replace(fmt.Sprintf("%v", newPart)) + if path == "" { + return "/" + key + } + if strings.HasSuffix(path, "/") { + return path + key + } + return path + "/" + key +} + +// diff returns the (recursive) difference between a and b as an array of JsonPatchOperations. +func diff(a, b map[string]interface{}, path string, patch []Operation) ([]Operation, error) { + for key, bv := range b { + p := makePath(path, key) + av, ok := a[key] + // value was added + if !ok { + patch = append(patch, NewOperation("add", p, bv)) + continue + } + // Types are the same, compare values + var err error + patch, err = handleValues(av, bv, p, patch) + if err != nil { + return nil, err + } + } + // Now add all deleted values as nil + for key := range a { + _, found := b[key] + if !found { + p := makePath(path, key) + + patch = append(patch, NewOperation("remove", p, nil)) + } + } + return patch, nil +} + +func handleValues(av, bv interface{}, p string, patch []Operation) ([]Operation, error) { + { + at := reflect.TypeOf(av) + bt := reflect.TypeOf(bv) + if at == nil && bt == nil { + // do nothing + return patch, nil + } else if at == nil && bt != nil { + return append(patch, NewOperation("add", p, bv)), nil + } else if at != bt { + // If types have changed, replace completely (preserves null in destination) + return append(patch, NewOperation("replace", p, bv)), nil + } + } + + var err error + switch at := av.(type) { + case map[string]interface{}: + bt := bv.(map[string]interface{}) + patch, err = diff(at, bt, p, patch) + if err != nil { + return nil, err + } + case string, float64, bool: + if !matchesValue(av, bv) { + patch = append(patch, NewOperation("replace", p, bv)) + } + case []interface{}: + bt := bv.([]interface{}) + if isSimpleArray(at) && isSimpleArray(bt) { + patch = append(patch, compareEditDistance(at, bt, p)...) + } else { + n := min(len(at), len(bt)) + for i := len(at) - 1; i >= n; i-- { + patch = append(patch, NewOperation("remove", makePath(p, i), nil)) + } + for i := n; i < len(bt); i++ { + patch = append(patch, NewOperation("add", makePath(p, i), bt[i])) + } + for i := 0; i < n; i++ { + var err error + patch, err = handleValues(at[i], bt[i], makePath(p, i), patch) + if err != nil { + return nil, err + } + } + } + default: + panic(fmt.Sprintf("Unknown type:%T ", av)) + } + return patch, nil +} + +func isBasicType(a interface{}) bool { + switch a.(type) { + case string, float64, bool: + default: + return false + } + return true +} + +func isSimpleArray(a []interface{}) bool { + for i := range a { + switch a[i].(type) { + case string, float64, bool: + default: + val := reflect.ValueOf(a[i]) + if val.Kind() == reflect.Map { + for _, k := range val.MapKeys() { + av := val.MapIndex(k) + if av.Kind() == reflect.Ptr || av.Kind() == reflect.Interface { + if av.IsNil() { + continue + } + av = av.Elem() + } + if av.Kind() != reflect.String && av.Kind() != reflect.Float64 && av.Kind() != reflect.Bool { + return false + } + } + return true + } + return false + } + } + return true +} + +// https://en.wikipedia.org/wiki/Wagner%E2%80%93Fischer_algorithm +// Adapted from https://github.com/texttheater/golang-levenshtein +func compareEditDistance(s, t []interface{}, p string) []Operation { + m := len(s) + n := len(t) + + d := make([][]int, m+1) + for i := 0; i <= m; i++ { + d[i] = make([]int, n+1) + d[i][0] = i + } + for j := 0; j <= n; j++ { + d[0][j] = j + } + + for j := 1; j <= n; j++ { + for i := 1; i <= m; i++ { + if reflect.DeepEqual(s[i-1], t[j-1]) { + d[i][j] = d[i-1][j-1] // no op required + } else { + del := d[i-1][j] + 1 + add := d[i][j-1] + 1 + rep := d[i-1][j-1] + 1 + d[i][j] = min(rep, min(add, del)) + } + } + } + + return backtrace(s, t, p, m, n, d) +} + +func min(x int, y int) int { + if y < x { + return y + } + return x +} + +func backtrace(s, t []interface{}, p string, i int, j int, matrix [][]int) []Operation { + if i > 0 && matrix[i-1][j]+1 == matrix[i][j] { + op := NewOperation("remove", makePath(p, i-1), nil) + return append([]Operation{op}, backtrace(s, t, p, i-1, j, matrix)...) + } + if j > 0 && matrix[i][j-1]+1 == matrix[i][j] { + op := NewOperation("add", makePath(p, i), t[j-1]) + return append([]Operation{op}, backtrace(s, t, p, i, j-1, matrix)...) + } + if i > 0 && j > 0 && matrix[i-1][j-1]+1 == matrix[i][j] { + if isBasicType(s[0]) { + op := NewOperation("replace", makePath(p, i-1), t[j-1]) + return append([]Operation{op}, backtrace(s, t, p, i-1, j-1, matrix)...) + } + + p2, _ := handleValues(s[i-1], t[j-1], makePath(p, i-1), []Operation{}) + return append(p2, backtrace(s, t, p, i-1, j-1, matrix)...) + } + if i > 0 && j > 0 && matrix[i-1][j-1] == matrix[i][j] { + return backtrace(s, t, p, i-1, j-1, matrix) + } + return []Operation{} +} From 28b217e2c9ac7b0b18fbb1fc1abda8a911a49e55 Mon Sep 17 00:00:00 2001 From: Thomas Shafer Date: Fri, 27 Mar 2020 14:13:29 -0700 Subject: [PATCH 2/2] Replace reconciler with genreconciler. Delete base reconciler. --- pkg/reconciler/knativeserving/controller.go | 33 ++-- ...erving_controller.go => knativeserving.go} | 144 +++++++++--------- pkg/reconciler/reconciler.go | 124 --------------- pkg/reconciler/reconciler_test.go | 54 ------- 4 files changed, 98 insertions(+), 257 deletions(-) rename pkg/reconciler/knativeserving/{knativeserving_controller.go => knativeserving.go} (67%) delete mode 100644 pkg/reconciler/reconciler.go delete mode 100644 pkg/reconciler/reconciler_test.go diff --git a/pkg/reconciler/knativeserving/controller.go b/pkg/reconciler/knativeserving/controller.go index 8b2c80e0..fd3d4f1c 100644 --- a/pkg/reconciler/knativeserving/controller.go +++ b/pkg/reconciler/knativeserving/controller.go @@ -19,7 +19,10 @@ import ( "os" "path/filepath" + kubeclient "knative.dev/pkg/client/injection/kube/client" "knative.dev/pkg/injection/sharedmain" + "knative.dev/pkg/logging" + servingclient "knative.dev/serving-operator/pkg/client/injection/client" "github.com/go-logr/zapr" mfc "github.com/manifestival/client-go-client" @@ -30,13 +33,13 @@ import ( "knative.dev/pkg/controller" "knative.dev/serving-operator/pkg/apis/serving/v1alpha1" knativeServinginformer "knative.dev/serving-operator/pkg/client/injection/informers/serving/v1alpha1/knativeserving" - rbase "knative.dev/serving-operator/pkg/reconciler" + knsreconciler "knative.dev/serving-operator/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving" + "knative.dev/serving-operator/pkg/reconciler" "knative.dev/serving-operator/pkg/reconciler/knativeserving/common" ) const ( controllerAgentName = "knativeserving-controller" - reconcilerName = "KnativeServing" ) var ( @@ -52,33 +55,41 @@ func NewController( ) *controller.Impl { knativeServingInformer := knativeServinginformer.Get(ctx) deploymentInformer := deploymentinformer.Get(ctx) + logger := logging.FromContext(ctx) + + statsReporter, err := reconciler.NewStatsReporter(controllerAgentName) + if err != nil { + logger.Fatal(err) + } c := &Reconciler{ - Base: rbase.NewBase(ctx, controllerAgentName, cmw), - knativeServingLister: knativeServingInformer.Lister(), - servings: map[string]int64{}, - platform: common.GetPlatforms(ctx), + kubeClientSet: kubeclient.Get(ctx), + knativeServingClientSet: servingclient.Get(ctx), + statsReporter: statsReporter, + knativeServingLister: knativeServingInformer.Lister(), + servings: map[string]int64{}, + platform: common.GetPlatforms(ctx), } koDataDir := os.Getenv("KO_DATA_PATH") cfg, err := sharedmain.GetConfig(*MasterURL, *Kubeconfig) if err != nil { - c.Logger.Error(err, "Error building kubeconfig") + logger.Error(err, "Error building kubeconfig") } config, err := mfc.NewManifest(filepath.Join(koDataDir, "knative-serving/"), cfg, - mf.UseLogger(zapr.NewLogger(c.Logger.Desugar()).WithName("manifestival"))) + mf.UseLogger(zapr.NewLogger(logger.Desugar()).WithName("manifestival"))) if err != nil { - c.Logger.Error(err, "Error creating the Manifest for knative-serving") + logger.Error(err, "Error creating the Manifest for knative-serving") os.Exit(1) } c.config = config - impl := controller.NewImpl(c, c.Logger, reconcilerName) + impl := knsreconciler.NewImpl(ctx, c) - c.Logger.Info("Setting up event handlers for %s", reconcilerName) + logger.Info("Setting up event handlers") knativeServingInformer.Informer().AddEventHandler(controller.HandleAll(impl.Enqueue)) diff --git a/pkg/reconciler/knativeserving/knativeserving_controller.go b/pkg/reconciler/knativeserving/knativeserving.go similarity index 67% rename from pkg/reconciler/knativeserving/knativeserving_controller.go rename to pkg/reconciler/knativeserving/knativeserving.go index d9982e5b..0d96dcda 100644 --- a/pkg/reconciler/knativeserving/knativeserving_controller.go +++ b/pkg/reconciler/knativeserving/knativeserving.go @@ -21,23 +21,25 @@ import ( "fmt" mf "github.com/manifestival/manifestival" + clientset "knative.dev/serving-operator/pkg/client/clientset/versioned" + "go.uber.org/zap" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/equality" "k8s.io/apimachinery/pkg/api/errors" - apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - + "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/cache" - - "knative.dev/pkg/controller" + "knative.dev/pkg/logging" servingv1alpha1 "knative.dev/serving-operator/pkg/apis/serving/v1alpha1" + knsreconciler "knative.dev/serving-operator/pkg/client/injection/reconciler/serving/v1alpha1/knativeserving" listers "knative.dev/serving-operator/pkg/client/listers/serving/v1alpha1" "knative.dev/serving-operator/pkg/reconciler" "knative.dev/serving-operator/pkg/reconciler/knativeserving/common" "knative.dev/serving-operator/version" + + pkgreconciler "knative.dev/pkg/reconciler" ) const ( @@ -54,7 +56,13 @@ var ( // Reconciler implements controller.Reconciler for Knativeserving resources. type Reconciler struct { - *reconciler.Base + // kubeClientSet allows us to talk to the k8s for core APIs + kubeClientSet kubernetes.Interface + // knativeServingClientSet allows us to configure Serving objects + knativeServingClientSet clientset.Interface + // statsReporter reports reconciler's metrics. + statsReporter reconciler.StatsReporter + // Listers index properties about resources knativeServingLister listers.KnativeServingLister config mf.Manifest @@ -64,38 +72,41 @@ type Reconciler struct { } // Check that our Reconciler implements controller.Reconciler -var _ controller.Reconciler = (*Reconciler)(nil) +var _ knsreconciler.Interface = (*Reconciler)(nil) +var _ knsreconciler.Finalizer = (*Reconciler)(nil) -// Reconcile compares the actual state with the desired, and attempts to -// converge the two. It then updates the Status block of the Knativeserving resource -// with the current status of the resource. -func (r *Reconciler) Reconcile(ctx context.Context, key string) error { - // Convert the namespace/name string into a distinct namespace and name - namespace, name, err := cache.SplitMetaNamespaceKey(key) +// FinalizeKind removes all resources after deletion of a KnativeServing. +func (r *Reconciler) FinalizeKind(ctx context.Context, original *servingv1alpha1.KnativeServing) pkgreconciler.Event { + logger := logging.FromContext(ctx) + + key, err := cache.MetaNamespaceKeyFunc(original) if err != nil { - r.Logger.Errorf("invalid resource key: %s", key) + logger.Errorf("invalid resource key: %s", key) return nil } - // Get the KnativeServing resource with this namespace/name. - original, err := r.knativeServingLister.KnativeServings(namespace).Get(name) - if apierrs.IsNotFound(err) { - return nil - } else if err != nil { - r.Logger.Error(err, "Error getting KnativeServing") - return err + if _, ok := r.servings[key]; ok { + delete(r.servings, key) } - if original.GetDeletionTimestamp() != nil { - if _, ok := r.servings[key]; ok { - delete(r.servings, key) - r.StatsReporter.ReportKnativeservingChange(key, deletionChange) - } - return r.delete(original) + return r.delete(ctx, original) +} + +// ReconcileKind compares the actual state with the desired, and attempts to +// converge the two. +func (r *Reconciler) ReconcileKind(ctx context.Context, original *servingv1alpha1.KnativeServing) pkgreconciler.Event { + logger := logging.FromContext(ctx) + + // Convert the namespace/name string into a distinct namespace and name + key, err := cache.MetaNamespaceKeyFunc(original) + if err != nil { + logger.Errorf("invalid resource key: %s", key) + return nil } + // Keep track of the number and generation of KnativeServings in the cluster. newGen := original.Generation if oldGen, ok := r.servings[key]; ok { if newGen > oldGen { - r.StatsReporter.ReportKnativeservingChange(key, editChange) + r.statsReporter.ReportKnativeservingChange(key, editChange) } else if newGen < oldGen { return fmt.Errorf("reconciling obsolete generation of KnativeServing %s: newGen = %d and oldGen = %d", key, newGen, oldGen) } @@ -103,40 +114,27 @@ func (r *Reconciler) Reconcile(ctx context.Context, key string) error { // No metrics are emitted when newGen > 1: the first reconciling of // a new operator on an existing KnativeServing resource. if newGen == 1 { - r.StatsReporter.ReportKnativeservingChange(key, creationChange) + r.statsReporter.ReportKnativeservingChange(key, creationChange) } } - r.servings[key] = newGen - - // Don't modify the informers copy. - knativeServing := original.DeepCopy() + r.servings[key] = original.Generation // Reconcile this copy of the KnativeServing resource and then write back any status // updates regardless of whether the reconciliation errored out. - reconcileErr := r.reconcile(ctx, knativeServing) - if equality.Semantic.DeepEqual(original.Status, knativeServing.Status) { - // If we didn't change anything then don't call updateStatus. - // This is important because the copy we loaded from the informer's - // cache may be stale and we don't want to overwrite a prior update - // to status with this stale state. - } else if err = r.updateStatus(knativeServing); err != nil { - r.Logger.Warnw("Failed to update knativeServing status", zap.Error(err)) - r.Recorder.Eventf(knativeServing, corev1.EventTypeWarning, "UpdateFailed", - "Failed to update status for KnativeServing %q: %v", knativeServing.Name, err) + err = r.reconcile(ctx, original) + if err != nil { return err } - if reconcileErr != nil { - r.Recorder.Event(knativeServing, corev1.EventTypeWarning, "InternalError", reconcileErr.Error()) - return reconcileErr - } return nil } func (r *Reconciler) reconcile(ctx context.Context, ks *servingv1alpha1.KnativeServing) error { - reqLogger := r.Logger.With(zap.String("Request.Namespace", ks.Namespace)).With("Request.Name", ks.Name) + logger := logging.FromContext(ctx) + + reqLogger := logger.With(zap.String("Request.Namespace", ks.Namespace)).With("Request.Name", ks.Name) reqLogger.Infow("Reconciling KnativeServing", "status", ks.Status) - stages := []func(*mf.Manifest, *servingv1alpha1.KnativeServing) error{ + stages := []func(context.Context, *mf.Manifest, *servingv1alpha1.KnativeServing) error{ r.ensureFinalizer, r.initStatus, r.install, @@ -144,14 +142,14 @@ func (r *Reconciler) reconcile(ctx context.Context, ks *servingv1alpha1.KnativeS r.deleteObsoleteResources, } - manifest, err := r.transform(ks) + manifest, err := r.transform(ctx, ks) if err != nil { ks.Status.MarkInstallFailed(err.Error()) return err } for _, stage := range stages { - if err := stage(&manifest, ks); err != nil { + if err := stage(ctx, &manifest, ks); err != nil { return err } } @@ -160,9 +158,11 @@ func (r *Reconciler) reconcile(ctx context.Context, ks *servingv1alpha1.KnativeS } // Transform the resources -func (r *Reconciler) transform(instance *servingv1alpha1.KnativeServing) (mf.Manifest, error) { - r.Logger.Debug("Transforming manifest") - transforms, err := r.platform.Transformers(r.KubeClientSet, instance, r.Logger) +func (r *Reconciler) transform(ctx context.Context, instance *servingv1alpha1.KnativeServing) (mf.Manifest, error) { + logger := logging.FromContext(ctx) + + logger.Debug("Transforming manifest") + transforms, err := r.platform.Transformers(r.kubeClientSet, instance, logger) if err != nil { return mf.Manifest{}, err } @@ -171,7 +171,7 @@ func (r *Reconciler) transform(instance *servingv1alpha1.KnativeServing) (mf.Man // Update the status subresource func (r *Reconciler) updateStatus(instance *servingv1alpha1.KnativeServing) error { - afterUpdate, err := r.KnativeServingClientSet.OperatorV1alpha1().KnativeServings(instance.Namespace).UpdateStatus(instance) + afterUpdate, err := r.knativeServingClientSet.OperatorV1alpha1().KnativeServings(instance.Namespace).UpdateStatus(instance) if err != nil { return err @@ -182,8 +182,10 @@ func (r *Reconciler) updateStatus(instance *servingv1alpha1.KnativeServing) erro } // Initialize status conditions -func (r *Reconciler) initStatus(_ *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { - r.Logger.Debug("Initializing status") +func (r *Reconciler) initStatus(ctx context.Context, _ *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { + logger := logging.FromContext(ctx) + + logger.Debug("Initializing status") if len(instance.Status.Conditions) == 0 { instance.Status.InitializeConditions() if err := r.updateStatus(instance); err != nil { @@ -194,8 +196,10 @@ func (r *Reconciler) initStatus(_ *mf.Manifest, instance *servingv1alpha1.Knativ } // Apply the manifest resources -func (r *Reconciler) install(manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { - r.Logger.Debug("Installing manifest") +func (r *Reconciler) install(ctx context.Context, manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { + logger := logging.FromContext(ctx) + + logger.Debug("Installing manifest") // The Operator needs a higher level of permissions if it 'bind's non-existent roles. // To avoid this, we strictly order the manifest application as (Cluster)Roles, then // (Cluster)RoleBindings, then the rest of the manifest. @@ -217,8 +221,10 @@ func (r *Reconciler) install(manifest *mf.Manifest, instance *servingv1alpha1.Kn } // Check for all deployments available -func (r *Reconciler) checkDeployments(manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { - r.Logger.Debug("Checking deployments") +func (r *Reconciler) checkDeployments(ctx context.Context, manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { + logger := logging.FromContext(ctx) + + logger.Debug("Checking deployments") available := func(d *appsv1.Deployment) bool { for _, c := range d.Status.Conditions { if c.Type == appsv1.DeploymentAvailable && c.Status == corev1.ConditionTrue { @@ -228,7 +234,7 @@ func (r *Reconciler) checkDeployments(manifest *mf.Manifest, instance *servingv1 return false } for _, u := range manifest.Filter(mf.ByKind("Deployment")).Resources() { - deployment, err := r.KubeClientSet.AppsV1().Deployments(u.GetNamespace()).Get(u.GetName(), metav1.GetOptions{}) + deployment, err := r.kubeClientSet.AppsV1().Deployments(u.GetNamespace()).Get(u.GetName(), metav1.GetOptions{}) if err != nil { instance.Status.MarkDeploymentsNotReady() if errors.IsNotFound(err) { @@ -246,23 +252,25 @@ func (r *Reconciler) checkDeployments(manifest *mf.Manifest, instance *servingv1 } // ensureFinalizer attaches a "delete manifest" finalizer to the instance -func (r *Reconciler) ensureFinalizer(manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { +func (r *Reconciler) ensureFinalizer(ctx context.Context, manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { for _, finalizer := range instance.GetFinalizers() { if finalizer == finalizerName { return nil } } instance.SetFinalizers(append(instance.GetFinalizers(), finalizerName)) - instance, err := r.KnativeServingClientSet.OperatorV1alpha1().KnativeServings(instance.Namespace).Update(instance) + _, err := r.knativeServingClientSet.OperatorV1alpha1().KnativeServings(instance.Namespace).Update(instance) return err } // delete all the resources in the release manifest -func (r *Reconciler) delete(instance *servingv1alpha1.KnativeServing) error { +func (r *Reconciler) delete(ctx context.Context, instance *servingv1alpha1.KnativeServing) error { + logger := logging.FromContext(ctx) + if len(instance.GetFinalizers()) == 0 || instance.GetFinalizers()[0] != finalizerName { return nil } - r.Logger.Info("Deleting resources") + logger.Info("Deleting resources") var RBAC = mf.Any(mf.ByKind("Role"), mf.ByKind("ClusterRole"), mf.ByKind("RoleBinding"), mf.ByKind("ClusterRoleBinding")) if len(r.servings) == 0 { if err := r.config.Filter(mf.ByKind("Deployment")).Delete(); err != nil { @@ -282,12 +290,12 @@ func (r *Reconciler) delete(instance *servingv1alpha1.KnativeServing) error { return err } refetched.SetFinalizers(refetched.GetFinalizers()[1:]) - _, err = r.KnativeServingClientSet.OperatorV1alpha1().KnativeServings(refetched.Namespace).Update(refetched) + _, err = r.knativeServingClientSet.OperatorV1alpha1().KnativeServings(refetched.Namespace).Update(refetched) return err } // Delete obsolete resources from previous versions -func (r *Reconciler) deleteObsoleteResources(manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { +func (r *Reconciler) deleteObsoleteResources(ctx context.Context, manifest *mf.Manifest, instance *servingv1alpha1.KnativeServing) error { // istio-system resources from 0.3 resource := &unstructured.Unstructured{} resource.SetNamespace("istio-system") diff --git a/pkg/reconciler/reconciler.go b/pkg/reconciler/reconciler.go deleted file mode 100644 index 62752dfc..00000000 --- a/pkg/reconciler/reconciler.go +++ /dev/null @@ -1,124 +0,0 @@ -/* -Copyright 2019 The Knative 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 - - https://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 reconciler - -import ( - "context" - - "go.uber.org/zap" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/dynamic" - "k8s.io/client-go/kubernetes" - "k8s.io/client-go/kubernetes/scheme" - typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/tools/record" - - kubeclient "knative.dev/pkg/client/injection/kube/client" - "knative.dev/pkg/configmap" - "knative.dev/pkg/controller" - "knative.dev/pkg/injection/clients/dynamicclient" - "knative.dev/pkg/logging" - "knative.dev/pkg/logging/logkey" - clientset "knative.dev/serving-operator/pkg/client/clientset/versioned" - servingScheme "knative.dev/serving-operator/pkg/client/clientset/versioned/scheme" - servingclient "knative.dev/serving-operator/pkg/client/injection/client" -) - -// Base implements the core controller logic, given a Reconciler. -type Base struct { - // KubeClientSet allows us to talk to the k8s for core APIs - KubeClientSet kubernetes.Interface - - // ServingClientSet allows us to configure Serving objects - KnativeServingClientSet clientset.Interface - - // DynamicClientSet allows us to configure pluggable Build objects - DynamicClientSet dynamic.Interface - - // ConfigMapWatcher allows us to watch for ConfigMap changes. - ConfigMapWatcher configmap.Watcher - - // Recorder is an event recorder for recording Event resources to the - // Kubernetes API. - Recorder record.EventRecorder - - // StatsReporter reports reconciler's metrics. - StatsReporter StatsReporter - - // Sugared logger is easier to use but is not as performant as the - // raw logger. In performance critical paths, call logger.Desugar() - // and use the returned raw logger instead. In addition to the - // performance benefits, raw logger also preserves type-safety at - // the expense of slightly greater verbosity. - Logger *zap.SugaredLogger -} - -// NewBase instantiates a new instance of Base implementing -// the common & boilerplate code between our reconcilers. -func NewBase(ctx context.Context, controllerAgentName string, cmw configmap.Watcher) *Base { - // Enrich the logs with controller name - logger := logging.FromContext(ctx). - Named(controllerAgentName). - With(zap.String(logkey.ControllerType, controllerAgentName)) - - kubeClient := kubeclient.Get(ctx) - - recorder := controller.GetEventRecorder(ctx) - if recorder == nil { - // Create event broadcaster - logger.Debug("Creating event broadcaster") - eventBroadcaster := record.NewBroadcaster() - watches := []watch.Interface{ - eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof), - eventBroadcaster.StartRecordingToSink( - &typedcorev1.EventSinkImpl{Interface: kubeClient.CoreV1().Events("")}), - } - recorder = eventBroadcaster.NewRecorder( - scheme.Scheme, corev1.EventSource{Component: controllerAgentName}) - go func() { - <-ctx.Done() - for _, w := range watches { - w.Stop() - } - }() - } - - // Create metrics reporter - statsReporter, err := NewStatsReporter(controllerAgentName) - if err != nil { - logger.Fatal(err) - } - - base := &Base{ - KubeClientSet: kubeClient, - KnativeServingClientSet: servingclient.Get(ctx), - DynamicClientSet: dynamicclient.Get(ctx), - ConfigMapWatcher: cmw, - Recorder: recorder, - StatsReporter: statsReporter, - Logger: logger, - } - - return base -} - -func init() { - // Add serving types to the default Kubernetes Scheme so Events can be - // logged for serving types. - servingScheme.AddToScheme(scheme.Scheme) -} diff --git a/pkg/reconciler/reconciler_test.go b/pkg/reconciler/reconciler_test.go deleted file mode 100644 index 83336722..00000000 --- a/pkg/reconciler/reconciler_test.go +++ /dev/null @@ -1,54 +0,0 @@ -/* -Copyright 2019 The Knative 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 reconciler - -import ( - "context" - "testing" - - _ "knative.dev/pkg/client/injection/kube/client/fake" - _ "knative.dev/pkg/injection/clients/dynamicclient/fake" - _ "knative.dev/serving-operator/pkg/client/injection/client/fake" - - "k8s.io/client-go/rest" - "knative.dev/pkg/configmap" - "knative.dev/pkg/injection" -) - -var reconcilerName = "test-reconciler" - -func TestNewBase(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - cfg := &rest.Config{} - ctx, _ = injection.Fake.SetupInformers(ctx, cfg) - - cmw := configmap.NewStaticWatcher() - - r := NewBase(ctx, reconcilerName, cmw) - - if r == nil { - t.Fatal("Expected NewBase to return a non-nil value") - } - if r.Recorder == nil { - t.Fatal("Expected NewBase to add a Recorder") - } - if r.StatsReporter == nil { - t.Fatal("Expected NewBase to add a StatsReporter") - } -}