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