mirror of https://github.com/knative/pkg.git
Add Reconciler for kube v1.Service (#2858)
This commit is contained in:
parent
e2256bd241
commit
ca8c009405
|
@ -0,0 +1,168 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 service
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
fmt "fmt"
|
||||||
|
reflect "reflect"
|
||||||
|
strings "strings"
|
||||||
|
|
||||||
|
zap "go.uber.org/zap"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
labels "k8s.io/apimachinery/pkg/labels"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
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"
|
||||||
|
service "knative.dev/pkg/client/injection/kube/informers/core/v1/service"
|
||||||
|
controller "knative.dev/pkg/controller"
|
||||||
|
logging "knative.dev/pkg/logging"
|
||||||
|
logkey "knative.dev/pkg/logging/logkey"
|
||||||
|
reconciler "knative.dev/pkg/reconciler"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
defaultControllerAgentName = "service-controller"
|
||||||
|
defaultFinalizerName = "services.core"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.ControllerOptions to be used by 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.Fatal("Up to one options function is supported, found: ", len(optionsFns))
|
||||||
|
}
|
||||||
|
|
||||||
|
serviceInformer := service.Get(ctx)
|
||||||
|
|
||||||
|
lister := serviceInformer.Lister()
|
||||||
|
|
||||||
|
var promoteFilterFunc func(obj interface{}) bool
|
||||||
|
var promoteFunc = func(bkt reconciler.Bucket) {}
|
||||||
|
|
||||||
|
rec := &reconcilerImpl{
|
||||||
|
LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
|
||||||
|
PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
|
||||||
|
|
||||||
|
// Signal promotion event
|
||||||
|
promoteFunc(bkt)
|
||||||
|
|
||||||
|
all, err := lister.List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, elt := range all {
|
||||||
|
if promoteFilterFunc != nil {
|
||||||
|
if ok := promoteFilterFunc(elt); !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
enq(bkt, types.NamespacedName{
|
||||||
|
Namespace: elt.GetNamespace(),
|
||||||
|
Name: elt.GetName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Client: client.Get(ctx),
|
||||||
|
Lister: lister,
|
||||||
|
reconciler: r,
|
||||||
|
finalizerName: defaultFinalizerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
ctrType := reflect.TypeOf(r).Elem()
|
||||||
|
ctrTypeName := fmt.Sprintf("%s.%s", ctrType.PkgPath(), ctrType.Name())
|
||||||
|
ctrTypeName = strings.ReplaceAll(ctrTypeName, "/", ".")
|
||||||
|
|
||||||
|
logger = logger.With(
|
||||||
|
zap.String(logkey.ControllerType, ctrTypeName),
|
||||||
|
zap.String(logkey.Kind, "core.Service"),
|
||||||
|
)
|
||||||
|
|
||||||
|
impl := controller.NewContext(ctx, rec, controller.ControllerOptions{WorkQueueName: ctrTypeName, Logger: logger})
|
||||||
|
agentName := defaultControllerAgentName
|
||||||
|
|
||||||
|
// Pass impl to the options. Save any optional results.
|
||||||
|
for _, fn := range optionsFns {
|
||||||
|
opts := fn(impl)
|
||||||
|
if opts.ConfigStore != nil {
|
||||||
|
rec.configStore = opts.ConfigStore
|
||||||
|
}
|
||||||
|
if opts.FinalizerName != "" {
|
||||||
|
rec.finalizerName = opts.FinalizerName
|
||||||
|
}
|
||||||
|
if opts.AgentName != "" {
|
||||||
|
agentName = opts.AgentName
|
||||||
|
}
|
||||||
|
if opts.SkipStatusUpdates {
|
||||||
|
rec.skipStatusUpdates = true
|
||||||
|
}
|
||||||
|
if opts.DemoteFunc != nil {
|
||||||
|
rec.DemoteFunc = opts.DemoteFunc
|
||||||
|
}
|
||||||
|
if opts.PromoteFilterFunc != nil {
|
||||||
|
promoteFilterFunc = opts.PromoteFilterFunc
|
||||||
|
}
|
||||||
|
if opts.PromoteFunc != nil {
|
||||||
|
promoteFunc = opts.PromoteFunc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rec.Recorder = createRecorder(ctx, agentName)
|
||||||
|
|
||||||
|
return impl
|
||||||
|
}
|
||||||
|
|
||||||
|
func createRecorder(ctx context.Context, agentName string) record.EventRecorder {
|
||||||
|
logger := logging.FromContext(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: agentName})
|
||||||
|
go func() {
|
||||||
|
<-ctx.Done()
|
||||||
|
for _, w := range watches {
|
||||||
|
w.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
return recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
scheme.AddToScheme(scheme.Scheme)
|
||||||
|
}
|
|
@ -0,0 +1,431 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 service
|
||||||
|
|
||||||
|
import (
|
||||||
|
context "context"
|
||||||
|
json "encoding/json"
|
||||||
|
fmt "fmt"
|
||||||
|
|
||||||
|
zap "go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
equality "k8s.io/apimachinery/pkg/api/equality"
|
||||||
|
errors "k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
labels "k8s.io/apimachinery/pkg/labels"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
sets "k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
kubernetes "k8s.io/client-go/kubernetes"
|
||||||
|
corev1 "k8s.io/client-go/listers/core/v1"
|
||||||
|
record "k8s.io/client-go/tools/record"
|
||||||
|
controller "knative.dev/pkg/controller"
|
||||||
|
kmp "knative.dev/pkg/kmp"
|
||||||
|
logging "knative.dev/pkg/logging"
|
||||||
|
reconciler "knative.dev/pkg/reconciler"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Interface defines the strongly typed interfaces to be implemented by a
|
||||||
|
// controller reconciling v1.Service.
|
||||||
|
type Interface interface {
|
||||||
|
// ReconcileKind implements custom logic to reconcile v1.Service. 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 *v1.Service) reconciler.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalizer defines the strongly typed interfaces to be implemented by a
|
||||||
|
// controller finalizing v1.Service.
|
||||||
|
type Finalizer interface {
|
||||||
|
// FinalizeKind implements custom logic to finalize v1.Service. 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 *v1.Service) reconciler.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadOnlyInterface defines the strongly typed interfaces to be implemented by a
|
||||||
|
// controller reconciling v1.Service if they want to process resources for which
|
||||||
|
// they are not the leader.
|
||||||
|
type ReadOnlyInterface interface {
|
||||||
|
// ObserveKind implements logic to observe v1.Service.
|
||||||
|
// This method should not write to the API.
|
||||||
|
ObserveKind(ctx context.Context, o *v1.Service) reconciler.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
type doReconcile func(ctx context.Context, o *v1.Service) reconciler.Event
|
||||||
|
|
||||||
|
// reconcilerImpl implements controller.Reconciler for v1.Service resources.
|
||||||
|
type reconcilerImpl struct {
|
||||||
|
// LeaderAwareFuncs is inlined to help us implement reconciler.LeaderAware.
|
||||||
|
reconciler.LeaderAwareFuncs
|
||||||
|
|
||||||
|
// Client is used to write back status updates.
|
||||||
|
Client kubernetes.Interface
|
||||||
|
|
||||||
|
// Listers index properties about resources.
|
||||||
|
Lister corev1.ServiceLister
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// finalizerName is the name of the finalizer to reconcile.
|
||||||
|
finalizerName string
|
||||||
|
|
||||||
|
// skipStatusUpdates configures whether or not this reconciler automatically updates
|
||||||
|
// the status of the reconciled resource.
|
||||||
|
skipStatusUpdates bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that our Reconciler implements controller.Reconciler.
|
||||||
|
var _ controller.Reconciler = (*reconcilerImpl)(nil)
|
||||||
|
|
||||||
|
// Check that our generated Reconciler is always LeaderAware.
|
||||||
|
var _ reconciler.LeaderAware = (*reconcilerImpl)(nil)
|
||||||
|
|
||||||
|
func NewReconciler(ctx context.Context, logger *zap.SugaredLogger, client kubernetes.Interface, lister corev1.ServiceLister, 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.Fatal("Up to one options struct is supported, found: ", len(options))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail fast when users inadvertently implement the other LeaderAware interface.
|
||||||
|
// For the typed reconcilers, Promote shouldn't take any arguments.
|
||||||
|
if _, ok := r.(reconciler.LeaderAware); ok {
|
||||||
|
logger.Fatalf("%T implements the incorrect LeaderAware interface. Promote() should not take an argument as genreconciler handles the enqueuing automatically.", r)
|
||||||
|
}
|
||||||
|
|
||||||
|
rec := &reconcilerImpl{
|
||||||
|
LeaderAwareFuncs: reconciler.LeaderAwareFuncs{
|
||||||
|
PromoteFunc: func(bkt reconciler.Bucket, enq func(reconciler.Bucket, types.NamespacedName)) error {
|
||||||
|
all, err := lister.List(labels.Everything())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, elt := range all {
|
||||||
|
// TODO: Consider letting users specify a filter in options.
|
||||||
|
enq(bkt, types.NamespacedName{
|
||||||
|
Namespace: elt.GetNamespace(),
|
||||||
|
Name: elt.GetName(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Client: client,
|
||||||
|
Lister: lister,
|
||||||
|
Recorder: recorder,
|
||||||
|
reconciler: r,
|
||||||
|
finalizerName: defaultFinalizerName,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, opts := range options {
|
||||||
|
if opts.ConfigStore != nil {
|
||||||
|
rec.configStore = opts.ConfigStore
|
||||||
|
}
|
||||||
|
if opts.FinalizerName != "" {
|
||||||
|
rec.finalizerName = opts.FinalizerName
|
||||||
|
}
|
||||||
|
if opts.SkipStatusUpdates {
|
||||||
|
rec.skipStatusUpdates = true
|
||||||
|
}
|
||||||
|
if opts.DemoteFunc != nil {
|
||||||
|
rec.DemoteFunc = opts.DemoteFunc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rec
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile implements controller.Reconciler
|
||||||
|
func (r *reconcilerImpl) Reconcile(ctx context.Context, key string) error {
|
||||||
|
logger := logging.FromContext(ctx)
|
||||||
|
|
||||||
|
// Initialize the reconciler state. This will convert the namespace/name
|
||||||
|
// string into a distinct namespace and name, determine if this instance of
|
||||||
|
// the reconciler is the leader, and any additional interfaces implemented
|
||||||
|
// by the reconciler. Returns an error is the resource key is invalid.
|
||||||
|
s, err := newState(key, r)
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Invalid resource key: ", key)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we are not the leader, and we don't implement either ReadOnly
|
||||||
|
// observer interfaces, then take a fast-path out.
|
||||||
|
if s.isNotLeaderNorObserver() {
|
||||||
|
return controller.NewSkipKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
|
||||||
|
// Get the resource with this namespace/name.
|
||||||
|
|
||||||
|
getter := r.Lister.Services(s.namespace)
|
||||||
|
|
||||||
|
original, err := getter.Get(s.name)
|
||||||
|
|
||||||
|
if errors.IsNotFound(err) {
|
||||||
|
// The resource may no longer exist, in which case we stop processing and call
|
||||||
|
// the ObserveDeletion handler if appropriate.
|
||||||
|
logger.Debugf("Resource %q no longer exists", key)
|
||||||
|
if del, ok := r.reconciler.(reconciler.OnDeletionInterface); ok {
|
||||||
|
return del.ObserveDeletion(ctx, types.NamespacedName{
|
||||||
|
Namespace: s.namespace,
|
||||||
|
Name: s.name,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
} else if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't modify the informers copy.
|
||||||
|
resource := original.DeepCopy()
|
||||||
|
|
||||||
|
var reconcileEvent reconciler.Event
|
||||||
|
|
||||||
|
name, do := s.reconcileMethodFor(resource)
|
||||||
|
// Append the target method to the logger.
|
||||||
|
logger = logger.With(zap.String("targetMethod", name))
|
||||||
|
switch name {
|
||||||
|
case reconciler.DoReconcileKind:
|
||||||
|
// Set and update the finalizer on resource if r.reconciler
|
||||||
|
// implements Finalizer.
|
||||||
|
if resource, err = r.setFinalizerIfFinalizer(ctx, resource); err != nil {
|
||||||
|
return fmt.Errorf("failed to set finalizers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconcile this copy of the resource and then write back any status
|
||||||
|
// updates regardless of whether the reconciliation errored out.
|
||||||
|
reconcileEvent = do(ctx, resource)
|
||||||
|
|
||||||
|
case reconciler.DoFinalizeKind:
|
||||||
|
// For finalizing reconcilers, if this resource being marked for deletion
|
||||||
|
// and reconciled cleanly (nil or normal event), remove the finalizer.
|
||||||
|
reconcileEvent = do(ctx, resource)
|
||||||
|
|
||||||
|
if resource, err = r.clearFinalizer(ctx, resource, reconcileEvent); err != nil {
|
||||||
|
return fmt.Errorf("failed to clear finalizers: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
case reconciler.DoObserveKind:
|
||||||
|
// Observe any changes to this resource, since we are not the leader.
|
||||||
|
reconcileEvent = do(ctx, resource)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the status.
|
||||||
|
switch {
|
||||||
|
case r.skipStatusUpdates:
|
||||||
|
// This reconciler implementation is configured to skip resource updates.
|
||||||
|
// This may mean this reconciler does not observe spec, but reconciles external changes.
|
||||||
|
case 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.
|
||||||
|
case !s.isLeader:
|
||||||
|
// High-availability reconcilers may have many replicas watching the resource, but only
|
||||||
|
// the elected leader is expected to write modifications.
|
||||||
|
logger.Warn("Saw status changes when we aren't the leader!")
|
||||||
|
default:
|
||||||
|
if err = r.updateStatus(ctx, logger, 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.Event(resource, event.EventType, event.Reason, event.Error())
|
||||||
|
|
||||||
|
// the event was wrapped inside an error, consider the reconciliation as failed
|
||||||
|
if _, isEvent := reconcileEvent.(*reconciler.ReconcilerEvent); !isEvent {
|
||||||
|
return reconcileEvent
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if controller.IsSkipKey(reconcileEvent) {
|
||||||
|
// This is a wrapped error, don't emit an event.
|
||||||
|
} else if ok, _ := controller.IsRequeueKey(reconcileEvent); ok {
|
||||||
|
// This is a wrapped error, don't emit an event.
|
||||||
|
} 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(ctx context.Context, logger *zap.SugaredLogger, existing *v1.Service, desired *v1.Service) 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.CoreV1().Services(desired.Namespace)
|
||||||
|
|
||||||
|
existing, err = getter.Get(ctx, desired.Name, metav1.GetOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there's nothing to update, just return.
|
||||||
|
if equality.Semantic.DeepEqual(existing.Status, desired.Status) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if logger.Desugar().Core().Enabled(zapcore.DebugLevel) {
|
||||||
|
if diff, err := kmp.SafeDiff(existing.Status, desired.Status); err == nil && diff != "" {
|
||||||
|
logger.Debug("Updating status with: ", diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existing.Status = desired.Status
|
||||||
|
|
||||||
|
updater := r.Client.CoreV1().Services(existing.Namespace)
|
||||||
|
|
||||||
|
_, err = updater.UpdateStatus(ctx, existing, metav1.UpdateOptions{})
|
||||||
|
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 or its override.
|
||||||
|
func (r *reconcilerImpl) updateFinalizersFiltered(ctx context.Context, resource *v1.Service, desiredFinalizers sets.String) (*v1.Service, error) {
|
||||||
|
// Don't modify the informers copy.
|
||||||
|
existing := resource.DeepCopy()
|
||||||
|
|
||||||
|
var finalizers []string
|
||||||
|
|
||||||
|
// If there's nothing to update, just return.
|
||||||
|
existingFinalizers := sets.NewString(existing.Finalizers...)
|
||||||
|
|
||||||
|
if desiredFinalizers.Has(r.finalizerName) {
|
||||||
|
if existingFinalizers.Has(r.finalizerName) {
|
||||||
|
// Nothing to do.
|
||||||
|
return resource, nil
|
||||||
|
}
|
||||||
|
// Add the finalizer.
|
||||||
|
finalizers = append(existing.Finalizers, r.finalizerName)
|
||||||
|
} else {
|
||||||
|
if !existingFinalizers.Has(r.finalizerName) {
|
||||||
|
// Nothing to do.
|
||||||
|
return resource, nil
|
||||||
|
}
|
||||||
|
// Remove the finalizer.
|
||||||
|
existingFinalizers.Delete(r.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.CoreV1().Services(resource.Namespace)
|
||||||
|
|
||||||
|
resourceName := resource.Name
|
||||||
|
updated, err := patcher.Patch(ctx, resourceName, types.MergePatchType, patch, metav1.PatchOptions{})
|
||||||
|
if err != nil {
|
||||||
|
r.Recorder.Eventf(existing, v1.EventTypeWarning, "FinalizerUpdateFailed",
|
||||||
|
"Failed to update finalizers for %q: %v", resourceName, err)
|
||||||
|
} else {
|
||||||
|
r.Recorder.Eventf(updated, v1.EventTypeNormal, "FinalizerUpdate",
|
||||||
|
"Updated %q finalizers", resource.GetName())
|
||||||
|
}
|
||||||
|
return updated, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reconcilerImpl) setFinalizerIfFinalizer(ctx context.Context, resource *v1.Service) (*v1.Service, 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(r.finalizerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the finalizers filtered by r.finalizerName.
|
||||||
|
return r.updateFinalizersFiltered(ctx, resource, finalizers)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *reconcilerImpl) clearFinalizer(ctx context.Context, resource *v1.Service, reconcileEvent reconciler.Event) (*v1.Service, 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(r.finalizerName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
finalizers.Delete(r.finalizerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Synchronize the finalizers filtered by r.finalizerName.
|
||||||
|
return r.updateFinalizersFiltered(ctx, resource, finalizers)
|
||||||
|
}
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Copyright 2022 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 service
|
||||||
|
|
||||||
|
import (
|
||||||
|
fmt "fmt"
|
||||||
|
|
||||||
|
v1 "k8s.io/api/core/v1"
|
||||||
|
types "k8s.io/apimachinery/pkg/types"
|
||||||
|
cache "k8s.io/client-go/tools/cache"
|
||||||
|
reconciler "knative.dev/pkg/reconciler"
|
||||||
|
)
|
||||||
|
|
||||||
|
// state is used to track the state of a reconciler in a single run.
|
||||||
|
type state struct {
|
||||||
|
// key is the original reconciliation key from the queue.
|
||||||
|
key string
|
||||||
|
// namespace is the namespace split from the reconciliation key.
|
||||||
|
namespace string
|
||||||
|
// name is the name split from the reconciliation key.
|
||||||
|
name string
|
||||||
|
// reconciler is the reconciler.
|
||||||
|
reconciler Interface
|
||||||
|
// roi is the read only interface cast of the reconciler.
|
||||||
|
roi ReadOnlyInterface
|
||||||
|
// isROI (Read Only Interface) the reconciler only observes reconciliation.
|
||||||
|
isROI bool
|
||||||
|
// isLeader the instance of the reconciler is the elected leader.
|
||||||
|
isLeader bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newState(key string, r *reconcilerImpl) (*state, error) {
|
||||||
|
// Convert the namespace/name string into a distinct namespace and name.
|
||||||
|
namespace, name, err := cache.SplitMetaNamespaceKey(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid resource key: %s", key)
|
||||||
|
}
|
||||||
|
|
||||||
|
roi, isROI := r.reconciler.(ReadOnlyInterface)
|
||||||
|
|
||||||
|
isLeader := r.IsLeaderFor(types.NamespacedName{
|
||||||
|
Namespace: namespace,
|
||||||
|
Name: name,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &state{
|
||||||
|
key: key,
|
||||||
|
namespace: namespace,
|
||||||
|
name: name,
|
||||||
|
reconciler: r.reconciler,
|
||||||
|
roi: roi,
|
||||||
|
isROI: isROI,
|
||||||
|
isLeader: isLeader,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// isNotLeaderNorObserver checks to see if this reconciler with the current
|
||||||
|
// state is enabled to do any work or not.
|
||||||
|
// isNotLeaderNorObserver returns true when there is no work possible for the
|
||||||
|
// reconciler.
|
||||||
|
func (s *state) isNotLeaderNorObserver() bool {
|
||||||
|
if !s.isLeader && !s.isROI {
|
||||||
|
// If we are not the leader, and we don't implement the ReadOnly
|
||||||
|
// interface, then take a fast-path out.
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *state) reconcileMethodFor(o *v1.Service) (string, doReconcile) {
|
||||||
|
if o.GetDeletionTimestamp().IsZero() {
|
||||||
|
if s.isLeader {
|
||||||
|
return reconciler.DoReconcileKind, s.reconciler.ReconcileKind
|
||||||
|
} else if s.isROI {
|
||||||
|
return reconciler.DoObserveKind, s.roi.ObserveKind
|
||||||
|
}
|
||||||
|
} else if fin, ok := s.reconciler.(Finalizer); s.isLeader && ok {
|
||||||
|
return reconciler.DoFinalizeKind, fin.FinalizeKind
|
||||||
|
}
|
||||||
|
return "unknown", nil
|
||||||
|
}
|
|
@ -51,7 +51,7 @@ EXTERNAL_INFORMER_PKG="k8s.io/client-go/informers" \
|
||||||
k8s.io/api \
|
k8s.io/api \
|
||||||
"${K8S_TYPES}" \
|
"${K8S_TYPES}" \
|
||||||
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt \
|
--go-header-file ${REPO_ROOT_DIR}/hack/boilerplate/boilerplate.go.txt \
|
||||||
--force-genreconciler-kinds "Namespace,ConfigMap,Deployment,Secret,Pod,CronJob,NetworkPolicy,Node,ValidatingWebhookConfiguration,MutatingWebhookConfiguration,ServiceAccount"
|
--force-genreconciler-kinds "Namespace,ConfigMap,Deployment,Secret,Service,Pod,CronJob,NetworkPolicy,Node,ValidatingWebhookConfiguration,MutatingWebhookConfiguration,ServiceAccount"
|
||||||
|
|
||||||
OUTPUT_PKG="knative.dev/pkg/client/injection/apiextensions" \
|
OUTPUT_PKG="knative.dev/pkg/client/injection/apiextensions" \
|
||||||
VERSIONED_CLIENTSET_PKG="k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" \
|
VERSIONED_CLIENTSET_PKG="k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset" \
|
||||||
|
|
Loading…
Reference in New Issue