knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/codegen/cmd/injection-gen/generators/reconciler/reconciler_controller.go (about)

     1  /*
     2  Copyright 2020 The Knative Authors
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package reconciler
    18  
    19  import (
    20  	"io"
    21  
    22  	"k8s.io/gengo/v2/generator"
    23  	"k8s.io/gengo/v2/namer"
    24  	"k8s.io/gengo/v2/types"
    25  	"k8s.io/klog/v2"
    26  )
    27  
    28  // reconcilerControllerGenerator produces a file for setting up the reconciler
    29  // with injection.
    30  type reconcilerControllerGenerator struct {
    31  	generator.GoGenerator
    32  	outputPackage  string
    33  	imports        namer.ImportTracker
    34  	typeToGenerate *types.Type
    35  
    36  	groupName           string
    37  	clientPkg           string
    38  	schemePkg           string
    39  	informerPackagePath string
    40  
    41  	reconcilerClasses  []string
    42  	hasReconcilerClass bool
    43  	hasStatus          bool
    44  }
    45  
    46  var _ generator.Generator = (*reconcilerControllerGenerator)(nil)
    47  
    48  func (g *reconcilerControllerGenerator) Filter(c *generator.Context, t *types.Type) bool {
    49  	// Only process the type for this generator.
    50  	return t == g.typeToGenerate
    51  }
    52  
    53  func (g *reconcilerControllerGenerator) Namers(c *generator.Context) namer.NameSystems {
    54  	return namer.NameSystems{
    55  		"raw": namer.NewRawNamer(g.outputPackage, g.imports),
    56  	}
    57  }
    58  
    59  func (g *reconcilerControllerGenerator) Imports(c *generator.Context) (imports []string) {
    60  	imports = append(imports, g.imports.ImportLines()...)
    61  	return imports
    62  }
    63  
    64  func (g *reconcilerControllerGenerator) GenerateType(c *generator.Context, t *types.Type, w io.Writer) error {
    65  	sw := generator.NewSnippetWriter(w, c, "{{", "}}")
    66  
    67  	klog.V(5).Info("processing type ", t)
    68  
    69  	m := map[string]interface{}{
    70  		"type":      t,
    71  		"group":     g.groupName,
    72  		"classes":   g.reconcilerClasses,
    73  		"hasClass":  g.hasReconcilerClass,
    74  		"hasStatus": g.hasStatus,
    75  		"controllerImpl": c.Universe.Type(types.Name{
    76  			Package: "knative.dev/pkg/controller",
    77  			Name:    "Impl",
    78  		}),
    79  		"controllerReconciler": c.Universe.Type(types.Name{
    80  			Package: "knative.dev/pkg/controller",
    81  			Name:    "Reconciler",
    82  		}),
    83  		"controllerNewContext": c.Universe.Function(types.Name{
    84  			Package: "knative.dev/pkg/controller",
    85  			Name:    "NewContext",
    86  		}),
    87  		"loggingFromContext": c.Universe.Function(types.Name{
    88  			Package: "knative.dev/pkg/logging",
    89  			Name:    "FromContext",
    90  		}),
    91  		"ptrString": c.Universe.Function(types.Name{
    92  			Package: "knative.dev/pkg/ptr",
    93  			Name:    "String",
    94  		}),
    95  		"corev1EventSource": c.Universe.Function(types.Name{
    96  			Package: "k8s.io/api/core/v1",
    97  			Name:    "EventSource",
    98  		}),
    99  		"clientGet": c.Universe.Function(types.Name{
   100  			Package: g.clientPkg,
   101  			Name:    "Get",
   102  		}),
   103  		"informerGet": c.Universe.Function(types.Name{
   104  			Package: g.informerPackagePath,
   105  			Name:    "Get",
   106  		}),
   107  		"schemeScheme": c.Universe.Function(types.Name{
   108  			Package: "k8s.io/client-go/kubernetes/scheme",
   109  			Name:    "Scheme",
   110  		}),
   111  		"schemeAddToScheme": c.Universe.Function(types.Name{
   112  			Package: g.schemePkg,
   113  			Name:    "AddToScheme",
   114  		}),
   115  		"kubeclientGet": c.Universe.Function(types.Name{
   116  			Package: "knative.dev/pkg/client/injection/kube/client",
   117  			Name:    "Get",
   118  		}),
   119  		"typedcorev1EventSinkImpl": c.Universe.Function(types.Name{
   120  			Package: "k8s.io/client-go/kubernetes/typed/core/v1",
   121  			Name:    "EventSinkImpl",
   122  		}),
   123  		"recordNewBroadcaster": c.Universe.Function(types.Name{
   124  			Package: "k8s.io/client-go/tools/record",
   125  			Name:    "NewBroadcaster",
   126  		}),
   127  		"watchInterface": c.Universe.Type(types.Name{
   128  			Package: "k8s.io/apimachinery/pkg/watch",
   129  			Name:    "Interface",
   130  		}),
   131  		"controllerGetEventRecorder": c.Universe.Function(types.Name{
   132  			Package: "knative.dev/pkg/controller",
   133  			Name:    "GetEventRecorder",
   134  		}),
   135  		"controllerOptions": c.Universe.Type(types.Name{
   136  			Package: "knative.dev/pkg/controller",
   137  			Name:    "ControllerOptions",
   138  		}),
   139  		"controllerOptionsFn": c.Universe.Type(types.Name{
   140  			Package: "knative.dev/pkg/controller",
   141  			Name:    "OptionsFn",
   142  		}),
   143  		"contextContext": c.Universe.Type(types.Name{
   144  			Package: "context",
   145  			Name:    "Context",
   146  		}),
   147  		"reconcilerLeaderAwareFuncs": c.Universe.Type(types.Name{
   148  			Package: "knative.dev/pkg/reconciler",
   149  			Name:    "LeaderAwareFuncs",
   150  		}),
   151  		"reconcilerBucket": c.Universe.Type(types.Name{
   152  			Package: "knative.dev/pkg/reconciler",
   153  			Name:    "Bucket",
   154  		}),
   155  		"typesNamespacedName": c.Universe.Type(types.Name{
   156  			Package: "k8s.io/apimachinery/pkg/types",
   157  			Name:    "NamespacedName",
   158  		}),
   159  		"labelsEverything": c.Universe.Function(types.Name{
   160  			Package: "k8s.io/apimachinery/pkg/labels",
   161  			Name:    "Everything",
   162  		}),
   163  		"stringsReplaceAll": c.Universe.Function(types.Name{
   164  			Package: "strings",
   165  			Name:    "ReplaceAll",
   166  		}),
   167  		"reflectTypeOf": c.Universe.Function(types.Name{
   168  			Package: "reflect",
   169  			Name:    "TypeOf",
   170  		}),
   171  		"fmtSprintf": c.Universe.Function(types.Name{
   172  			Package: "fmt",
   173  			Name:    "Sprintf",
   174  		}),
   175  		"logkeyControllerType": c.Universe.Constant(types.Name{
   176  			Package: "knative.dev/pkg/logging/logkey",
   177  			Name:    "ControllerType",
   178  		}),
   179  		"logkeyControllerKind": c.Universe.Constant(types.Name{
   180  			Package: "knative.dev/pkg/logging/logkey",
   181  			Name:    "Kind",
   182  		}),
   183  		"zapString": c.Universe.Function(types.Name{
   184  			Package: "go.uber.org/zap",
   185  			Name:    "String",
   186  		}),
   187  	}
   188  
   189  	sw.Do(reconcilerControllerNewImpl, m)
   190  
   191  	return sw.Error()
   192  }
   193  
   194  var reconcilerControllerNewImpl = `
   195  const (
   196  	defaultControllerAgentName = "{{.type|lowercaseSingular}}-controller"
   197  	defaultFinalizerName       = "{{.type|allLowercasePlural}}.{{.group}}"
   198  	{{if .hasClass }}
   199  	// ClassAnnotationKey points to the annotation for the class of this resource.
   200  	{{if gt (.classes | len) 1 }}// Deprecated: Use ClassAnnotationKeys given multiple keys exist
   201  	{{end -}}
   202  	ClassAnnotationKey = "{{ index .classes 0 }}"
   203  	{{end}}
   204  )
   205  
   206  {{if gt (.classes | len) 1 }}
   207  var (
   208  	// ClassAnnotationKeys points to the annotation for the class of this resource.
   209  	ClassAnnotationKeys = []string{
   210  		{{range $class := .classes}}"{{$class}}",
   211  		{{end}}
   212  	}
   213  )
   214  {{end}}
   215  
   216  // NewImpl returns a {{.controllerImpl|raw}} that handles queuing and feeding work from
   217  // the queue through an implementation of {{.controllerReconciler|raw}}, delegating to
   218  // the provided Interface and optional Finalizer methods. OptionsFn is used to return
   219  // {{.controllerOptions|raw}} to be used by the internal reconciler.
   220  func NewImpl(ctx {{.contextContext|raw}}, r Interface{{if .hasClass}}, classValue string{{end}}, optionsFns ...{{.controllerOptionsFn|raw}}) *{{.controllerImpl|raw}} {
   221  	logger := {{.loggingFromContext|raw}}(ctx)
   222  
   223  	// Check the options function input. It should be 0 or 1.
   224  	if len(optionsFns) > 1 {
   225  		logger.Fatal("Up to one options function is supported, found: ", len(optionsFns))
   226  	}
   227  
   228  	{{.type|lowercaseSingular}}Informer := {{.informerGet|raw}}(ctx)
   229  
   230  	lister := {{.type|lowercaseSingular}}Informer.Lister()
   231  
   232  	var promoteFilterFunc func(obj interface{}) bool
   233  	var promoteFunc = func(bkt {{.reconcilerBucket|raw}}) {}
   234  
   235  	rec := &reconcilerImpl{
   236  		LeaderAwareFuncs: {{.reconcilerLeaderAwareFuncs|raw}}{
   237  			PromoteFunc: func(bkt {{.reconcilerBucket|raw}}, enq func({{.reconcilerBucket|raw}}, {{.typesNamespacedName|raw}})) error {
   238  
   239  				// Signal promotion event
   240  				promoteFunc(bkt)
   241  
   242  				all, err := lister.List({{.labelsEverything|raw}}())
   243  				if err != nil {
   244  					return err
   245  				}
   246  				for _, elt := range all {
   247  					if promoteFilterFunc != nil {
   248  						if ok := promoteFilterFunc(elt); !ok {
   249  							continue
   250  						}
   251  					}
   252  					enq(bkt, {{.typesNamespacedName|raw}}{
   253  						Namespace: elt.GetNamespace(),
   254  						Name: elt.GetName(),
   255  					})
   256  				}
   257  				return nil
   258  			},
   259  		},
   260  		Client:  {{.clientGet|raw}}(ctx),
   261  		Lister:  lister,
   262  		reconciler:    r,
   263  		finalizerName: defaultFinalizerName,
   264  		{{if .hasClass}}classValue: classValue,{{end}}
   265  	}
   266  
   267  	ctrType := {{.reflectTypeOf|raw}}(r).Elem()
   268  	ctrTypeName := {{.fmtSprintf|raw}}("%s.%s", ctrType.PkgPath(), ctrType.Name())
   269  	ctrTypeName = {{.stringsReplaceAll|raw}}(ctrTypeName, "/", ".")
   270  
   271  	logger = logger.With(
   272  			{{.zapString|raw}}({{.logkeyControllerType|raw}}, ctrTypeName),
   273  			{{.zapString|raw}}({{.logkeyControllerKind|raw}}, "{{ printf "%s.%s" .group .type.Name.Name }}"),
   274  	)
   275  
   276  
   277  	impl := {{.controllerNewContext|raw}}(ctx, rec, {{ .controllerOptions|raw }}{WorkQueueName: ctrTypeName, Logger: logger})
   278  	agentName := defaultControllerAgentName
   279  
   280  	// Pass impl to the options. Save any optional results.
   281  	for _, fn := range optionsFns {
   282  		opts := fn(impl)
   283  		if opts.ConfigStore != nil {
   284  			rec.configStore = opts.ConfigStore
   285  		}
   286  		if opts.FinalizerName != "" {
   287  			rec.finalizerName = opts.FinalizerName
   288  		}
   289  		if opts.AgentName != "" {
   290  			agentName = opts.AgentName
   291  		}
   292  		{{- if .hasStatus}}
   293  		if opts.SkipStatusUpdates {
   294  			rec.skipStatusUpdates = true
   295  		}
   296  		{{- end}}
   297  		if opts.DemoteFunc != nil {
   298  			rec.DemoteFunc = opts.DemoteFunc
   299  		}
   300  		if opts.PromoteFilterFunc != nil {
   301  			promoteFilterFunc = opts.PromoteFilterFunc
   302  		}
   303  		if opts.PromoteFunc != nil {
   304  			promoteFunc = opts.PromoteFunc
   305  		}
   306  		if opts.UseServerSideApplyForFinalizers {
   307  			if opts.FinalizerFieldManager == "" {
   308  				logger.Fatal("FinalizerFieldManager must be provided when UseServerSideApplyForFinalizers is enabled")
   309  			}
   310  			rec.useServerSideApplyForFinalizers = true
   311  			rec.finalizerFieldManager = opts.FinalizerFieldManager
   312  			rec.forceApplyFinalizers = opts.ForceApplyFinalizers
   313  		}
   314  	}
   315  
   316  	rec.Recorder = createRecorder(ctx, agentName)
   317  
   318  	return impl
   319  }
   320  
   321  func createRecorder(ctx context.Context, agentName string) record.EventRecorder {
   322  	logger := {{.loggingFromContext|raw}}(ctx)
   323  
   324  	recorder := {{.controllerGetEventRecorder|raw}}(ctx)
   325  	if recorder == nil {
   326  		// Create event broadcaster
   327  		logger.Debug("Creating event broadcaster")
   328  		eventBroadcaster := {{.recordNewBroadcaster|raw}}()
   329  		watches := []{{.watchInterface|raw}}{
   330  			eventBroadcaster.StartLogging(logger.Named("event-broadcaster").Infof),
   331  			eventBroadcaster.StartRecordingToSink(
   332  				&{{.typedcorev1EventSinkImpl|raw}}{Interface: {{.kubeclientGet|raw}}(ctx).CoreV1().Events("")}),
   333  		}
   334  		recorder = eventBroadcaster.NewRecorder({{.schemeScheme|raw}}, {{.corev1EventSource|raw}}{Component: agentName})
   335  		go func() {
   336  			<-ctx.Done()
   337  			for _, w := range watches {
   338  				w.Stop()
   339  			}
   340  		}()
   341  	}
   342  
   343  	return recorder
   344  }
   345  
   346  func init() {
   347  	{{.schemeAddToScheme|raw}}({{.schemeScheme|raw}})
   348  }
   349  `