github.com/fabianvf/ocp-release-operator-sdk@v0.0.0-20190426141702-57620ee2f090/doc/migration/v0.1.0-migration-guide.md (about) 1 # Migration Guide from v0.0.x to v0.1.0 2 3 This document describes how to migrate an operator project built using Operator SDK `v0.0.x` to the project structure required by `v0.1.0`. 4 5 The recommended way to migrate your project is to initialize a new `v0.1.0` project, then copy your code into the new project and modify as described below. 6 7 This guide goes over migrating the memcached-operator, an example project from the user guide, to illustrate migration steps. See the [v0.0.7 memcached-operator][v0.0.7-memcached-operator] and [v0.1.0 memcached-operator][v0.1.0-memcached-operator] project structures for pre- and post-migration examples, respectively. 8 9 ## Create a new v0.1.0 project 10 11 Rename your `v0.0.x` project and create a new `v0.1.0` project in its place. 12 13 ```sh 14 # Ensure SDK version is v0.1.0 15 $ operator-sdk --version 16 operator-sdk version 0.1.0 17 18 # Create new project 19 $ cd $GOPATH/src/github.com/example-inc/ 20 $ mv memcached-operator old-memcached-operator 21 $ operator-sdk new memcached-operator --skip-git-init 22 $ ls 23 memcached-operator old-memcached-operator 24 25 # Copy over .git from old project 26 $ cp -rf old-memcached-operator/.git memcached-operator/.git 27 ``` 28 29 ## Migrate custom types from pkg/apis 30 31 ### Scaffold api for custom types 32 33 Create the api for your custom resource (CR) in the new project with `operator-sdk add api --api-version=<apiversion> --kind=<kind>` 34 ```sh 35 $ cd memcached-operator 36 $ operator-sdk add api --api-version=cache.example.com/v1alpha1 --kind=Memcached 37 38 $ tree pkg/apis 39 pkg/apis/ 40 ├── addtoscheme_cache_v1alpha1.go 41 ├── apis.go 42 └── cache 43 └── v1alpha1 44 ├── doc.go 45 ├── memcached_types.go 46 ├── register.go 47 └── zz_generated.deepcopy.go 48 ``` 49 50 Repeat the above command for as many custom types as you had defined in your old project. Each type will be defined in the file `pkg/apis/<group>/<version>/<kind>_types.go`. 51 52 ### Copy the contents of the type 53 54 Copy the `Spec` and `Status` contents of the `pkg/apis/<group>/<version>/types.go` file from the old project to the new project's `pkg/apis/<group>/<version>/<kind>_types.go` file. 55 56 **Note:** Each `<kind>_types.go` file has an `init()` function. Be sure not to remove that since that registers the type with the Manager's scheme. 57 ```Go 58 func init() { 59 SchemeBuilder.Register(&Memcached{}, &MemcachedList{}) 60 } 61 ``` 62 63 ## Migrate reconcile code 64 65 ### Add a controller to watch your CR 66 67 In a `v0.0.x` project you would define what resource to watch in `cmd/<operator-name>/main.go` 68 ```Go 69 sdk.Watch("cache.example.com/v1alpha1", "Memcached", "default", time.Duration(5)*time.Second) 70 ``` 71 72 For a `v0.1.0` project you define a [Controller][controller-go-doc] to watch resources. 73 74 Add a controller to watch your CR type with `operator-sdk add controller --api-version=<apiversion> --kind=<kind>`. 75 ``` 76 $ operator-sdk add controller --api-version=cache.example.com/v1alpha1 --kind=Memcached 77 $ tree pkg/controller 78 pkg/controller/ 79 ├── add_memcached.go 80 ├── controller.go 81 └── memcached 82 └── memcached_controller.go 83 ``` 84 85 Inspect the `add()` function in your `pkg/controller/<kind>/<kind>_controller.go` file: 86 ```Go 87 import ( 88 cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" 89 ... 90 ) 91 92 func add(mgr manager.Manager, r reconcile.Reconciler) error { 93 c, err := controller.New("memcached-controller", mgr, controller.Options{Reconciler: r}) 94 95 // Watch for changes to the primary resource Memcached 96 err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) 97 98 // Watch for changes to the secondary resource Pods and enqueue reconcile requests for the owner Memcached 99 err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ 100 IsController: true, 101 OwnerType: &cachev1alpha1.Memcached{}, 102 }) 103 } 104 ``` 105 Remove the second `Watch()` or modify it to watch a secondary resource type that is owned by your CR. 106 107 Watching multiple resources lets you trigger the reconcile loop for multiple resources relevant to your application. See the [watching and eventhandling][watching-eventhandling-doc] doc and the Kubernetes [controller conventions][controller-conventions] doc for more details. 108 109 #### Multiple custom resources 110 111 If your operator is watching more than 1 CR type then you can do one of the following depending on your application: 112 - If the CR is owned by your primary CR then watch it as a secondary resource in the same controller to trigger the reconcile loop for the primary resource. 113 ```Go 114 // Watch for changes to the primary resource Memcached 115 err = c.Watch(&source.Kind{Type: &cachev1alpha1.Memcached{}}, &handler.EnqueueRequestForObject{}) 116 117 // Watch for changes to the secondary resource AppService and enqueue reconcile requests for the owner Memcached 118 err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForOwner{ 119 IsController: true, 120 OwnerType: &cachev1alpha1.Memcached{}, 121 }) 122 ``` 123 - Add a new controller to watch and reconcile the CR independently of the other CR. 124 ```sh 125 $ operator-sdk add controller --api-version=app.example.com/v1alpha1 --kind=AppService 126 ``` 127 ```Go 128 // Watch for changes to the primary resource AppService 129 err = c.Watch(&source.Kind{Type: &appv1alpha1.AppService{}}, &handler.EnqueueRequestForObject{}) 130 ``` 131 132 ### Copy and modify reconcile code from pkg/stub/handler.go 133 134 In a `v0.1.0` project the reconcile code is defined in the `Reconcile()` method of a controller's [Reconciler][reconciler-go-doc]. This is similar to the `Handle()` function in the older project. Note the difference in the arguments and return values: 135 - Reconcile 136 ```Go 137 func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) 138 ``` 139 - Handle 140 ```Go 141 func (h *Handler) Handle(ctx context.Context, event sdk.Event) error 142 ``` 143 144 Instead of receiving an `sdk.Event` (with the object), the `Reconcile()` function receives a [Request][request-go-doc] (Name/Namespace key) to lookup the object. 145 146 If the `Reconcile()` function returns an error, the controller will requeue and retry the `Request`. If no error is returned, then depending on the [Result][result-go-doc] the controller will either not retry the `Request`, immediately retry, or retry after a specified duration. 147 148 Copy the code from the old project's `Handle()` function over the existing code in your controller's `Reconcile()` function. 149 Be sure to keep the initial section in the `Reconcile()` code that looks up the object for the `Request` and checks to see if it's deleted. 150 151 ```Go 152 import ( 153 apierrors "k8s.io/apimachinery/pkg/api/errors" 154 cachev1alpha1 "github.com/example-inc/memcached-operator/pkg/apis/cache/v1alpha1" 155 ... 156 ) 157 func (r *ReconcileMemcached) Reconcile(request reconcile.Request) (reconcile.Result, error) { 158 // Fetch the Memcached instance 159 instance := &cachev1alpha1.Memcached{} 160 err := r.client.Get(context.TODO() 161 request.NamespacedName, instance) 162 if err != nil { 163 if apierrors.IsNotFound(err) { 164 // Request object not found, could have been deleted after reconcile request. 165 // Owned objects are automatically garbage collected. 166 // Return and don't requeue 167 return reconcile.Result{}, nil 168 } 169 // Error reading the object - requeue the request. 170 return reconcile.Result{}, err 171 } 172 173 // Rest of your reconcile code goes here. 174 ... 175 } 176 ``` 177 #### Update return values 178 179 Change the return values in your reconcile code: 180 - Replace `return err` with `return reconcile.Result{}, err` 181 - Replace `return nil` with `return reconcile.Result{}, nil` 182 183 #### Periodic reconcile 184 In order to periodically reconcile a CR in your controller you can set the [RequeueAfter][result-go-doc] field for reconcile.Result. 185 This will cause the controller to requeue the `Request` and trigger the reconcile after the desired duration. Note that the default value of 0 means no requeue. 186 187 ```Go 188 reconcilePeriod := 30 * time.Second 189 reconcileResult := reconcile.Result{RequeueAfter: reconcilePeriod} 190 ... 191 192 // Update the status 193 err := r.client.Update(context.TODO(), memcached) 194 if err != nil { 195 log.Info(fmt.Sprintf("Failed to update memcached status: %v", err)) 196 return reconcileResult, err 197 } 198 return reconcileResult, nil 199 200 ``` 201 202 #### Update client 203 204 Replace the calls to the SDK client(Create, Update, Delete, Get, List) with the reconciler's client. 205 206 See the examples below and the controller-runtime [client API doc][client-api-doc] for more details. 207 208 ```Go 209 // Create 210 dep := &appsv1.Deployment{...} 211 // v0.0.1 212 err := sdk.Create(dep) 213 // v0.1.0 214 err := r.client.Create(context.TODO(), dep) 215 216 // Update 217 // v0.1.0 218 err := sdk.Update(dep) 219 // v0.1.0 220 err := r.client.Update(context.TODO(), dep) 221 222 // Delete 223 err := sdk.Delete(dep) 224 // v0.1.0 225 err := r.client.Delete(context.TODO(), dep) 226 227 // List 228 podList := &corev1.PodList{} 229 labelSelector := labels.SelectorFromSet(labelsForMemcached(memcached.Name)) 230 listOps := &metav1.ListOptions{LabelSelector: labelSelector} 231 err := sdk.List(memcached.Namespace, podList, sdk.WithListOptions(listOps)) 232 // v0.1.0 233 listOps := &client.ListOptions{Namespace: memcached.Namespace, LabelSelector: labelSelector} 234 err := r.client.List(context.TODO(), listOps, podList) 235 236 // Get 237 dep := &appsv1.Deployment{APIVersion: "apps/v1", Kind: "Deployment", Name: name, Namespace: namespace} 238 err := sdk.Get(dep) 239 // v0.1.0 240 dep := &appsv1.Deployment{} 241 err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dep) 242 // v0.1.0 with unstructured 243 dep := &unstructured.Unstructured{} 244 dep.SetGroupVersionKind(schema.GroupVersionKind{Group:"apps", Version: "v1", Kind:"Deployment"}) 245 err = r.client.Get(context.TODO(), types.NamespacedName{Name: name, Namespace: namespace}, dep) 246 ``` 247 248 Lastly copy and initialize any other fields that you may have had in your `Handler` struct into the `Reconcile<Kind>` struct: 249 250 ```Go 251 // newReconciler returns a new reconcile.Reconciler 252 func newReconciler(mgr manager.Manager) reconcile.Reconciler { 253 return &ReconcileMemcached{client: mgr.GetClient(), scheme: mgr.GetScheme(), foo: "bar"} 254 } 255 256 // ReconcileMemcached reconciles a Memcached object 257 type ReconcileMemcached struct { 258 client client.Client 259 scheme *runtime.Scheme 260 // Other fields 261 foo string 262 } 263 ``` 264 265 ### Copy changes from main.go 266 267 The main function for a `v0.1.0` operator in `cmd/manager/main.go` sets up the [Manager][manager-go-doc] which registers the custom resources and starts all the controllers. 268 269 There is no need to migrate the SDK functions `sdk.Watch()`,`sdk.Handle()`, and `sdk.Run()` from the old `main.go` since that logic is now defined in a controller. 270 271 However if there are any operator specific flags or settings defined in the old main file copy those over. 272 273 If you have any 3rd party resource types registered with the SDK's scheme, then register those with the Manager's scheme in the new project. See how to [register 3rd party resources][register-3rd-party-resources]. 274 275 `operator-sdk` now expects `cmd/manager/main.go` to be present in Go operator projects. Go project-specific commands, ex. `add [api, controller]`, will error if `main.go` is not found in its expected path. 276 277 ### Copy user defined files 278 279 If there are any user defined pkgs, scripts, and docs in the older project, copy these files into the new project. 280 281 ### Copy changes to deployment manifests 282 283 For any updates made to the following manifests in the old project, copy over the changes to their corresponding files in the new project. Be careful not to directly overwrite the files but inspect and make any changes necessary. 284 - `tmp/build/Dockerfile` to `build/Dockerfile` 285 - There is no tmp directory in the new project layout 286 - RBAC rules updates from `deploy/rbac.yaml` to `deploy/role.yaml` and `deploy/role_binding.yaml` 287 - `deploy/cr.yaml` to `deploy/crds/<group>_<version>_<kind>_cr.yaml` 288 - `deploy/crd.yaml` to `deploy/crds/<group>_<version>_<kind>_crd.yaml` 289 290 ### Copy user defined dependencies 291 292 For any user defined dependencies added to the old project's Gopkg.toml, copy and append them to the new project's Gopkg.toml. 293 Run `dep ensure` to update the vendor in the new project. 294 295 ### Confirmation 296 297 At this point you should be able to build and run your operator to verify that it works. See the [user-guide][user-guide-build-run] on how to build and run your operator. 298 299 [v0.1.0-changes-doc]: ./v0.1.0-changes.md 300 [v0.0.7-memcached-operator]: https://github.com/operator-framework/operator-sdk-samples/tree/aa15bd278eec0959595e0a0a7282a26055d7f9d6/memcached-operator 301 [v0.1.0-memcached-operator]: https://github.com/operator-framework/operator-sdk-samples/tree/4c6934448684a6953ece4d3d9f3f77494b1c125e/memcached-operator 302 [controller-conventions]: https://github.com/kubernetes/community/blob/master/contributors/devel/controllers.md#guidelines 303 [reconciler-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/reconcile#Reconciler 304 [watching-eventhandling-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg#hdr-Watching_and_EventHandling 305 [controller-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg#hdr-Controller 306 [request-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/reconcile#Request 307 [result-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/reconcile#Result 308 [client-api-doc]: ./../user/client.md 309 [manager-go-doc]: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/manager 310 [register-3rd-party-resources]: ./../user-guide.md#adding-3rd-party-resources-to-your-operator 311 [user-guide-build-run]: ./../user-guide.md#build-and-run-the-operator