github.com/joelanford/operator-sdk@v0.8.2/internal/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 corev1 "k8s.io/api/core/v1" 47 "k8s.io/apimachinery/pkg/api/errors" 48 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 49 "k8s.io/apimachinery/pkg/runtime" 50 "k8s.io/apimachinery/pkg/types" 51 "sigs.k8s.io/controller-runtime/pkg/client" 52 "sigs.k8s.io/controller-runtime/pkg/controller" 53 "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 54 "sigs.k8s.io/controller-runtime/pkg/handler" 55 "sigs.k8s.io/controller-runtime/pkg/manager" 56 "sigs.k8s.io/controller-runtime/pkg/reconcile" 57 logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 58 "sigs.k8s.io/controller-runtime/pkg/source" 59 ) 60 61 var log = logf.Log.WithName("controller_appservice") 62 63 /** 64 * USER ACTION REQUIRED: This is a scaffold file intended for the user to modify with their own Controller 65 * business logic. Delete these comments after modifying this file.* 66 */ 67 68 // Add creates a new AppService Controller and adds it to the Manager. The Manager will set fields on the Controller 69 // and Start it when the Manager is Started. 70 func Add(mgr manager.Manager) error { 71 return add(mgr, newReconciler(mgr)) 72 } 73 74 // newReconciler returns a new reconcile.Reconciler 75 func newReconciler(mgr manager.Manager) reconcile.Reconciler { 76 return &ReconcileAppService{client: mgr.GetClient(), scheme: mgr.GetScheme()} 77 } 78 79 // add adds a new Controller to mgr with r as the reconcile.Reconciler 80 func add(mgr manager.Manager, r reconcile.Reconciler) error { 81 // Create a new controller 82 c, err := controller.New("appservice-controller", mgr, controller.Options{Reconciler: r}) 83 if err != nil { 84 return err 85 } 86 87 // Watch for changes to primary resource AppService 88 err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForObject{}) 89 if err != nil { 90 return err 91 } 92 93 // TODO(user): Modify this to be the types you create that are owned by the primary resource 94 // Watch for changes to secondary resource Pods and requeue the owner AppService 95 err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ 96 IsController: true, 97 OwnerType: &appv1alpha1.AppService{}, 98 }) 99 if err != nil { 100 return err 101 } 102 103 return nil 104 } 105 106 // blank assignment to verify that ReconcileAppService implements reconcile.Reconciler 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 ` 194 195 func TestGetCustomAPIImportPathAndIdentifier(t *testing.T) { 196 cases := []struct { 197 inputImport, wantImportPath, wantImportIdent string 198 wantErr bool 199 }{ 200 {"", "", "", true}, 201 {"=rbacv1", "", "", true}, 202 {"k8s.io/api/rbac-2/v1", "k8s.io/api/rbac-2/v1", "rbac2v1", false}, 203 {"k8s.io/api/rbac/v1=rbacv1", "k8s.io/api/rbac/v1", "rbacv1", false}, 204 {"k8s.io/api/rbac/v1=rbac-v1", "k8s.io/api/rbac/v1", "rbacv1", false}, 205 {"k8s.io/api/rb_ac/v1", "k8s.io/api/rb_ac/v1", "rb_acv1", false}, 206 {"k8s.io/api/rbac/v1=rbaC_v1", "k8s.io/api/rbac/v1", "rbaC_v1", false}, 207 {"k8s.io/api/rbac/v1=", "", "", true}, 208 {"k8s.io/api/rbac/v1=rbacv1=", "k8s.io/api/rbac/v1", "rbacv1", false}, 209 {"k8s.io/api/rbac/v1=Rbacv1", "k8s.io/api/rbac/v1", "Rbacv1", false}, 210 } 211 212 for _, c := range cases { 213 gotPath, gotIdent, err := getCustomAPIImportPathAndIdent(c.inputImport) 214 if err != nil && !c.wantErr { 215 t.Errorf(`wanted import path "%s" and identifier "%s" from "%s", got error %v`, c.wantImportPath, c.wantImportIdent, c.inputImport, err) 216 continue 217 } 218 if err == nil && c.wantErr { 219 t.Errorf(`wanted error from "%s", got import path "%s" and identifier "%s"`, c.inputImport, c.wantImportPath, c.wantImportIdent) 220 continue 221 } 222 if gotPath != c.wantImportPath { 223 t.Errorf(`wanted import path "%s" from "%s", got "%s"`, c.wantImportPath, c.inputImport, gotPath) 224 } 225 if gotIdent != c.wantImportIdent { 226 t.Errorf(`wanted import identifier "%s" from "%s", got "%s"`, c.wantImportIdent, c.inputImport, gotIdent) 227 } 228 } 229 }