github.com/jmrodri/operator-sdk@v0.5.0/pkg/scaffold/controller_kind.go (about) 1 // Copyright 2018 The Operator-SDK Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package scaffold 16 17 import ( 18 "path/filepath" 19 20 "github.com/operator-framework/operator-sdk/pkg/scaffold/input" 21 ) 22 23 // ControllerKind is the input needed to generate a pkg/controller/<kind>/<kind>_controller.go file 24 type ControllerKind struct { 25 input.Input 26 27 // Resource defines the inputs for the controller's primary resource 28 Resource *Resource 29 } 30 31 func (s *ControllerKind) GetInput() (input.Input, error) { 32 if s.Path == "" { 33 fileName := s.Resource.LowerKind + "_controller.go" 34 s.Path = filepath.Join(ControllerDir, s.Resource.LowerKind, fileName) 35 } 36 // Error if this file exists. 37 s.IfExistsAction = input.Error 38 s.TemplateBody = controllerKindTemplate 39 return s.Input, nil 40 } 41 42 const controllerKindTemplate = `package {{ .Resource.LowerKind }} 43 44 import ( 45 "context" 46 47 {{ .Resource.Group}}{{ .Resource.Version }} "{{ .Repo }}/pkg/apis/{{ .Resource.Group}}/{{ .Resource.Version }}" 48 49 corev1 "k8s.io/api/core/v1" 50 "k8s.io/apimachinery/pkg/api/errors" 51 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 52 "k8s.io/apimachinery/pkg/runtime" 53 "k8s.io/apimachinery/pkg/types" 54 "sigs.k8s.io/controller-runtime/pkg/client" 55 "sigs.k8s.io/controller-runtime/pkg/controller" 56 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 57 "sigs.k8s.io/controller-runtime/pkg/handler" 58 "sigs.k8s.io/controller-runtime/pkg/manager" 59 "sigs.k8s.io/controller-runtime/pkg/reconcile" 60 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 61 "sigs.k8s.io/controller-runtime/pkg/source" 62 ) 63 64 var log = logf.Log.WithName("controller_{{ .Resource.LowerKind }}") 65 66 /** 67 * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller 68 * business logic. Delete these comments after modifying this file.* 69 */ 70 71 // Add creates a new {{ .Resource.Kind }} Controller and adds it to the Manager. The Manager will set fields on the Controller 72 // and Start it when the Manager is Started. 73 func Add(mgr manager.Manager) error { 74 return add(mgr, newReconciler(mgr)) 75 } 76 77 // newReconciler returns a new reconcile.Reconciler 78 func newReconciler(mgr manager.Manager) reconcile.Reconciler { 79 return &Reconcile{{ .Resource.Kind }}{client: mgr.GetClient(), scheme: mgr.GetScheme()} 80 } 81 82 // add adds a new Controller to mgr with r as the reconcile.Reconciler 83 func add(mgr manager.Manager, r reconcile.Reconciler) error { 84 // Create a new controller 85 c, err := controller.New("{{ .Resource.LowerKind }}-controller", mgr, controller.Options{Reconciler: r}) 86 if err != nil { 87 return err 88 } 89 90 // Watch for changes to primary resource {{ .Resource.Kind }} 91 err = c.Watch(&source.Kind{Type: &{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}{}}, &handler.EnqueueRequestForObject{}) 92 if err != nil { 93 return err 94 } 95 96 // TODO(user): Modify this to be the types you create that are owned by the primary resource 97 // Watch for changes to secondary resource Pods and requeue the owner {{ .Resource.Kind }} 98 err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ 99 IsController: true, 100 OwnerType: &{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}{}, 101 }) 102 if err != nil { 103 return err 104 } 105 106 return nil 107 } 108 109 var _ reconcile.Reconciler = &Reconcile{{ .Resource.Kind }}{} 110 111 // Reconcile{{ .Resource.Kind }} reconciles a {{ .Resource.Kind }} object 112 type Reconcile{{ .Resource.Kind }} struct { 113 // This client, initialized using mgr.Client() above, is a split client 114 // that reads objects from the cache and writes to the apiserver 115 client client.Client 116 scheme *runtime.Scheme 117 } 118 119 // Reconcile reads that state of the cluster for a {{ .Resource.Kind }} object and makes changes based on the state read 120 // and what is in the {{ .Resource.Kind }}.Spec 121 // TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates 122 // a Pod as an example 123 // Note: 124 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 125 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 126 func (r *Reconcile{{ .Resource.Kind }}) Reconcile(request reconcile.Request) (reconcile.Result, error) { 127 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 128 reqLogger.Info("Reconciling {{ .Resource.Kind }}") 129 130 // Fetch the {{ .Resource.Kind }} instance 131 instance := &{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}{} 132 err := r.client.Get(context.TODO(), request.NamespacedName, instance) 133 if err != nil { 134 if errors.IsNotFound(err) { 135 // Request object not found, could have been deleted after reconcile request. 136 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 137 // Return and don't requeue 138 return reconcile.Result{}, nil 139 } 140 // Error reading the object - requeue the request. 141 return reconcile.Result{}, err 142 } 143 144 // Define a new Pod object 145 pod := newPodForCR(instance) 146 147 // Set {{ .Resource.Kind }} instance as the owner and controller 148 if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil { 149 return reconcile.Result{}, err 150 } 151 152 // Check if this Pod already exists 153 found := &corev1.Pod{} 154 err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, found) 155 if err != nil && errors.IsNotFound(err) { 156 reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name) 157 err = r.client.Create(context.TODO(), pod) 158 if err != nil { 159 return reconcile.Result{}, err 160 } 161 162 // Pod created successfully - don't requeue 163 return reconcile.Result{}, nil 164 } else if err != nil { 165 return reconcile.Result{}, err 166 } 167 168 // Pod already exists - don't requeue 169 reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name) 170 return reconcile.Result{}, nil 171 } 172 173 // newPodForCR returns a busybox pod with the same name/namespace as the cr 174 func newPodForCR(cr *{{ .Resource.Group}}{{ .Resource.Version }}.{{ .Resource.Kind }}) *corev1.Pod { 175 labels := map[string]string{ 176 "app": cr.Name, 177 } 178 return &corev1.Pod{ 179 ObjectMeta: metav1.ObjectMeta{ 180 Name: cr.Name + "-pod", 181 Namespace: cr.Namespace, 182 Labels: labels, 183 }, 184 Spec: corev1.PodSpec{ 185 Containers: []corev1.Container{ 186 { 187 Name: "busybox", 188 Image: "busybox", 189 Command: []string{"sleep", "3600"}, 190 }, 191 }, 192 }, 193 } 194 } 195 `