github.com/jmrodri/operator-sdk@v0.5.0/pkg/scaffold/controller_kind_test.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 "testing" 19 20 "github.com/operator-framework/operator-sdk/internal/util/diffutil" 21 ) 22 23 func TestControllerKind(t *testing.T) { 24 r, err := NewResource(appApiVersion, appKind) 25 if err != nil { 26 t.Fatal(err) 27 } 28 s, buf := setupScaffoldAndWriter() 29 err = s.Execute(appConfig, &ControllerKind{Resource: r}) 30 if err != nil { 31 t.Fatalf("Failed to execute the scaffold: (%v)", err) 32 } 33 34 if controllerKindExp != buf.String() { 35 diffs := diffutil.Diff(controllerKindExp, buf.String()) 36 t.Fatalf("Expected vs actual differs.\n%v", diffs) 37 } 38 } 39 40 const controllerKindExp = `package appservice 41 42 import ( 43 "context" 44 45 appv1alpha1 "github.com/example-inc/app-operator/pkg/apis/app/v1alpha1" 46 47 corev1 "k8s.io/api/core/v1" 48 "k8s.io/apimachinery/pkg/api/errors" 49 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 50 "k8s.io/apimachinery/pkg/runtime" 51 "k8s.io/apimachinery/pkg/types" 52 "sigs.k8s.io/controller-runtime/pkg/client" 53 "sigs.k8s.io/controller-runtime/pkg/controller" 54 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 55 "sigs.k8s.io/controller-runtime/pkg/handler" 56 "sigs.k8s.io/controller-runtime/pkg/manager" 57 "sigs.k8s.io/controller-runtime/pkg/reconcile" 58 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 59 "sigs.k8s.io/controller-runtime/pkg/source" 60 ) 61 62 var log = logf.Log.WithName("controller_appservice") 63 64 /** 65 * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller 66 * business logic. Delete these comments after modifying this file.* 67 */ 68 69 // Add creates a new AppService Controller and adds it to the Manager. The Manager will set fields on the Controller 70 // and Start it when the Manager is Started. 71 func Add(mgr manager.Manager) error { 72 return add(mgr, newReconciler(mgr)) 73 } 74 75 // newReconciler returns a new reconcile.Reconciler 76 func newReconciler(mgr manager.Manager) reconcile.Reconciler { 77 return &ReconcileAppService{client: mgr.GetClient(), scheme: mgr.GetScheme()} 78 } 79 80 // add adds a new Controller to mgr with r as the reconcile.Reconciler 81 func add(mgr manager.Manager, r reconcile.Reconciler) error { 82 // Create a new controller 83 c, err := controller.New("appservice-controller", mgr, controller.Options{Reconciler: r}) 84 if err != nil { 85 return err 86 } 87 88 // Watch for changes to primary resource AppService 89 err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForObject{}) 90 if err != nil { 91 return err 92 } 93 94 // TODO(user): Modify this to be the types you create that are owned by the primary resource 95 // Watch for changes to secondary resource Pods and requeue the owner AppService 96 err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ 97 IsController: true, 98 OwnerType: &appv1alpha1.AppService{}, 99 }) 100 if err != nil { 101 return err 102 } 103 104 return nil 105 } 106 107 var _ reconcile.Reconciler = &ReconcileAppService{} 108 109 // ReconcileAppService reconciles a AppService object 110 type ReconcileAppService struct { 111 // This client, initialized using mgr.Client() above, is a split client 112 // that reads objects from the cache and writes to the apiserver 113 client client.Client 114 scheme *runtime.Scheme 115 } 116 117 // Reconcile reads that state of the cluster for a AppService object and makes changes based on the state read 118 // and what is in the AppService.Spec 119 // TODO(user): Modify this Reconcile function to implement your Controller logic. This example creates 120 // a Pod as an example 121 // Note: 122 // The Controller will requeue the Request to be processed again if the returned error is non-nil or 123 // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 124 func (r *ReconcileAppService) Reconcile(request reconcile.Request) (reconcile.Result, error) { 125 reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 126 reqLogger.Info("Reconciling AppService") 127 128 // Fetch the AppService instance 129 instance := &appv1alpha1.AppService{} 130 err := r.client.Get(context.TODO(), request.NamespacedName, instance) 131 if err != nil { 132 if errors.IsNotFound(err) { 133 // Request object not found, could have been deleted after reconcile request. 134 // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 135 // Return and don't requeue 136 return reconcile.Result{}, nil 137 } 138 // Error reading the object - requeue the request. 139 return reconcile.Result{}, err 140 } 141 142 // Define a new Pod object 143 pod := newPodForCR(instance) 144 145 // Set AppService instance as the owner and controller 146 if err := controllerutil.SetControllerReference(instance, pod, r.scheme); err != nil { 147 return reconcile.Result{}, err 148 } 149 150 // Check if this Pod already exists 151 found := &corev1.Pod{} 152 err = r.client.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, found) 153 if err != nil && errors.IsNotFound(err) { 154 reqLogger.Info("Creating a new Pod", "Pod.Namespace", pod.Namespace, "Pod.Name", pod.Name) 155 err = r.client.Create(context.TODO(), pod) 156 if err != nil { 157 return reconcile.Result{}, err 158 } 159 160 // Pod created successfully - don't requeue 161 return reconcile.Result{}, nil 162 } else if err != nil { 163 return reconcile.Result{}, err 164 } 165 166 // Pod already exists - don't requeue 167 reqLogger.Info("Skip reconcile: Pod already exists", "Pod.Namespace", found.Namespace, "Pod.Name", found.Name) 168 return reconcile.Result{}, nil 169 } 170 171 // newPodForCR returns a busybox pod with the same name/namespace as the cr 172 func newPodForCR(cr *appv1alpha1.AppService) *corev1.Pod { 173 labels := map[string]string{ 174 "app": cr.Name, 175 } 176 return &corev1.Pod{ 177 ObjectMeta: metav1.ObjectMeta{ 178 Name: cr.Name + "-pod", 179 Namespace: cr.Namespace, 180 Labels: labels, 181 }, 182 Spec: corev1.PodSpec{ 183 Containers: []corev1.Container{ 184 { 185 Name: "busybox", 186 Image: "busybox", 187 Command: []string{"sleep", "3600"}, 188 }, 189 }, 190 }, 191 } 192 } 193 `