Repository: coryodaniel/bonny (elixir)

Issue:
Add mix bonny.init task
Add a task to initializing a new bonny operator.

* [x] add config/test.exs w/ HTTP and Discovery stubs
* [x] add example discovery mocks
* [x] add config/prod.exs defaulted to SA
* [x] add config/dev.exs default to docker-for-desktop?
* [ ] either a Makefile for building docker images or a task?
* [ ] bonny/operator logger, see [eviction operator](https://github.com/bonny-k8s/eviction_operator/blob/master/lib/eviction_operator/application.ex#L9)
* [x] test_helper.exs 
  * `K8s.Client.DynamicHTTPProvider.start_link([])`

Pull request description:
Add bonny.init mix task
closes #67

- [x] add mix task (inspired by #67)
- [x] add "getting started" guide
- [x] update README
- [x] add CHANGELOG

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27d5ada..0d7b9b4 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,10 +17,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - `Bonny.ControllerV2` as a successor to `Bonny.Controller`
   - Allows for better CRD definition ([#149](https://github.com/coryodaniel/bonny/pull/149))
   - Decouples CRD and controller ([#158](https://github.com/coryodaniel/bonny/pull/158))
+  - Operator wide API version configuration ([#159](https://github.com/coryodaniel/bonny/pull/159))
   - Adds `Bonny.EventRecorder` for creating Kubernetes events ([#156](https://github.com/coryodaniel/bonny/pull/156), [#5](https://github.com/coryodaniel/bonny/issues/5))
   - Allows skipping observed generations ([#153](https://github.com/coryodaniel/bonny/pull/153))
 - `Bonny.Resource.add_owner_reference/3` used to add the owner reference to resources created by the controller. ([#147](https://github.com/coryodaniel/bonny/pull/147))
 - An integration test suite was added that runs tests agains a "real" kubernetes cluster on the CI pipeline ([#146](https://github.com/coryodaniel/bonny/pull/146), [#84](https://github.com/coryodaniel/bonny/issues/84))
+- Mix task for initializing a new operator `mix bonny.init` ([#160](https://github.com/coryodaniel/bonny/pull/160), [#67](https://github.com/coryodaniel/bonny/issues/67))
 
 <!-- No new entries below this line! -->
 
diff --git a/README.md b/README.md
index 900fa10..09ae975 100644
--- a/README.md
+++ b/README.md
@@ -16,31 +16,16 @@ Bonny make it easy to create Kubernetes Operators, Controllers, and Custom [Sche
 
 If Kubernetes CRDs and controllers are new to you, read up on the [terminology](#terminology).
 
-## Tutorials and Examples:
-
-_Important!_ These tutorials are for an older version of Bonny, but the `add/1`, `modify/1`, and `delete/1` APIs are the same, as well as a new `reconcile/1` function. Additionally a [k8s](https://github.com/coryodaniel/k8s) has been added!
+## Getting Started
 
-Feel free to message me on [twitter](https://twitter.com/coryodaniel) if you need any help!
+Kickstarting your first controller with bonny is very straight-forward. Bonny
+comes with some handy mix tasks to help you.
 
-- HelloOperator Tutorial Part: [1](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-1-34ccb2ea0b4d) [2](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-2-efdf8e422085) [3](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-3-fdfc8b8cc843)
-- HelloOperator [source code](https://github.com/coryodaniel/hello_operator)
-
-## Talks
-
-- Commandeering Kubernetes @ The Big Elixir 2019
-  - [slides](https://speakerdeck.com/coryodaniel/commandeering-kubernetes-with-elixir)
-  - [source code](https://github.com/coryodaniel/talks/tree/master/commandeering)
-  - [video](https://www.youtube.com/watch?v=0r9YmbH0xTY)
-
-## Example Operators built with Bonny
-
-- [Eviction Operator](https://github.com/bonny-k8s/eviction_operator) - Bonny v 0.4
-- [Hello Operator](https://github.com/coryodaniel/hello_operator) - Bonny v 0.4
-- [Todo Operator](https://github.com/bonny-k8s/todo-operator) - Bonny v 0.4
-
-## Installation
+```
+mix new your_operator
+```
 
-Bonny can be installed by adding `bonny` to your list of dependencies in `mix.exs`:
+Now add bonny to your dependencies in `mix.exs`
 
 ```elixir
 def deps do
@@ -50,24 +35,41 @@ def deps do
 end
 ```
 
-### Configuration
+Install dependencies and initialize bonny. This task will ask you
+to answer a few questions about your controller
+
+Refer to the [kubernetes docs](https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups-and-versioning) for
+API group and API version.
+
+```
+mix deps.get
+mix bonny.init
+```
+
+Now you can generate your controllers.
+
+```
+mix bonny.gen.controller
+```
 
-#### Creating config files
+Again, you'll be asked questions regarding your controller. Refer to the [controllers guide](guides/controllers.livemd) for further information.
 
-[`mix new <project>` no longer creates initial config files](https://github.com/elixir-lang/elixir/issues/8815).
-If you want to follow theses setup instructions you will need to create one.
-You can find the full documentation for [`Config` here](https://hexdocs.pm/elixir/1.13.2/Config.html).
+Don't forget to add your controller to the list of controllers in `config/bonny.exs`
+
+### Configuration
+
+`mix bonny.init` creates a configuration file `config/bonny.exs` and imports it to `config/config.exs` for you.
 
 #### Configuring Bonny
 
 Bonny uses the [k8s client](https://github.com/coryodaniel/k8s) under the hood.
 
-The only configuration parameters required are `:bonny` `controllers` and a `:get_conn` callback (Note: this file will not exist unless you created it in the previous step):
+The only configuration parameters required are `:bonny` `controllers` and a `:get_conn` callback (Note: this file will not exist unless you created it using `mix bonny.init`):
 
 ```elixir
 
 config :bonny,
-  # Add each CRD Controller module for this operator to load here
+  # Add each Controller module for this operator to load here
   # Defaults to none. This *must* be set.
   controllers: [
     MyApp.Controllers.WebServer,
@@ -113,13 +115,6 @@ config :bonny,
   }
 ```
 
-When configuring bonny to run _in your cluster_ the `mix bonny.gen.manifest` command will generate a service account for you. To use that service account configure the `k8s` library like the following:
-
-```elixir
-config :bonny,
-  get_conn: {K8s.Conn, :from_service_account, []}
-```
-
 ## Running outside of a cluster
 
 Running an operator outside of Kubernetes is not recommended for production use, but can be very useful when testing.
@@ -135,6 +130,8 @@ config :bonny,
   {K8s.Conn, :from_file, ["~/.kube/config", [context: "optional-alternate-context"]]}
 ```
 
+If you've used `mix bonny.init` to generate your config, it created a `YourOperator.Conn` module for you. You can edit that instead.
+
 3. If RBAC is enabled, you must have permissions for creating and modifying `CustomResourceDefinition`, `ClusterRole`, `ClusterRoleBinding` and `ServiceAccount`.
 4. Generate a manifest `mix bonny.gen.manifest` and install it using kubectl `kubectl apply -f manifest.yaml`
 
@@ -144,82 +141,36 @@ Now you are ready to run your operator
 iex -S mix
 ```
 
-## Bonny Generators
-
-There are a number of generators to help create a Kubernetes operator.
-
-`mix help | grep bonny`
-
-### Generating an operator controller
-
-An operator can have multiple controllers. Each controller handles the lifecycle of a custom resource.
-
-```shell
-mix bonny.gen.controller
-# or
-mix bonny.gen.controller Widget v1
-```
-
-Open up your controller and add functionality for your resource's lifecycles:
-
-- Apply (or Add/Modify)
-- Delete
-- Reconcile; periodically called with each every instance of a CRD's resources
-
-Each controller can create multiple resources.
-
-For example, a _todo app_ controller could deploy a `Deployment` and a `Service`.
-
-Your operator can also have multiple controllers if you want to support multiple resources in your operator!
-
-Check out the [guide](./guides/controllers.livemd):
-
-### Generating a dockerfile
-
-The following command will generate a dockerfile _for your operator_. This will need to be pushed to a docker repository that your Kubernetes cluster can access.
-
-Again, this Dockerfile is for your operator, not for the pods your operator may deploy.
-
-You can skip this step when developing by running your operator _external_ to the cluster.
-
-```shell
-mix bonny.gen.dockerfile
-
-export BONNY_IMAGE=YOUR_IMAGE_NAME_HERE
-docker build -t ${BONNY_IMAGE} .
-docker push ${BONNY_IMAGE}:latest
-```
+## Guides
 
-### Generating Kubernetes manifest for operator
+Have a look at the guides that come with this repository. Some can even be opened as a livebook.
 
-This will generate the entire manifest for this operator including:
+- [Mix Tasks](guides/mix_tasks.md)
+- [Controllers](guides/controllers.livemd)
+- [Migrations](guides/migrations.md)
+- [Testing Bonny (for collaborators)](guides/testing.livemd)
 
-- CRD manifests
-- RBAC
-- Service Account
-- Operator Deployment
-
-The operator manifest generator requires the `image` flag to be passed if you plan to deploy the operator in your cluster. This is the docker image URL of your operators docker image created by `mix bonny.gen.docker` above.
+## Tutorials and Examples:
 
-```shell
-mix bonny.gen.manifest --image ${BONNY_IMAGE}
-```
+_Important!_ These tutorials are for an older version of Bonny, but the `add/1`, `modify/1`, and `delete/1` APIs are the same, as well as a new `reconcile/1` function. Additionally a [k8s](https://github.com/coryodaniel/k8s) has been added!
 
-You may _omit_ the `--image` flag if you want to generate a manifest _without the deployment_ so that you can develop locally running the operator outside of the cluster.
+Feel free to message me on [twitter](https://twitter.com/coryodaniel) if you need any help!
 
-By default the manifest will generate the service account and deployment in the "default" namespace.
+- HelloOperator Tutorial Part: [1](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-1-34ccb2ea0b4d) [2](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-2-efdf8e422085) [3](https://medium.com/coryodaniel/bonny-extending-kubernetes-with-elixir-part-3-fdfc8b8cc843)
+- HelloOperator [source code](https://github.com/coryodaniel/hello_operator)
 
-_To set the namespace explicitly:_
+## Talks
 
-```shell
-mix bonny.gen.manifest --out - -n test
-```
+- Commandeering Kubernetes @ The Big Elixir 2019
+  - [slides](https://speakerdeck.com/coryodaniel/commandeering-kubernetes-with-elixir)
+  - [source code](https://github.com/coryodaniel/talks/tree/master/commandeering)
+  - [video](https://www.youtube.com/watch?v=0r9YmbH0xTY)
 
-_Alternatively you can apply it directly to kubectl_:
+## Example Operators built with an older version of Bonny
 
-```shell
-mix bonny.gen.manifest --out - -n test | kubectl apply -f - -n test
-```
+- [Eviction Operator](https://github.com/bonny-k8s/eviction_operator) - Bonny v 0.4
+- [Hello Operator](https://github.com/coryodaniel/hello_operator) - Bonny v 0.4
+- [Todo Operator](https://github.com/bonny-k8s/todo-operator) - Bonny v 0.4
 
 ## Telemetry
 
diff --git a/config/config.exs b/config/config.exs
index 4910e71..2d96f93 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -1,6 +1,6 @@
 import Config
 
-if Mix.env() == :test do
+if config_env() == :test do
   config :logger, level: :none
 
   config :k8s,
@@ -24,7 +24,7 @@ if Mix.env() == :test do
     api_version: "apiextensions.k8s.io/v1"
 end
 
-if Mix.env() == :dev do
+if config_env() == :dev do
   config :logger, level: :debug
 
   config :mix_test_watch,
diff --git a/coveralls.json b/coveralls.json
index 18b66d3..8263732 100644
--- a/coveralls.json
+++ b/coveralls.json
@@ -3,7 +3,8 @@
     "test/support/",
     "lib/mix/bonny.ex",
     "lib/bonny/telemetry.ex",
-    "lib/bonny/sys/logger.ex"
+    "lib/bonny/sys/logger.ex",
+    "lib/mix/tasks/bonny.init.ex"
   ],
   "default_stop_words": [
     "Logger",
@@ -12,7 +13,7 @@
     "defrecord",
     "defstruct",
     "defimpl",
-    "def.+(.+\/\/.+).+do"
+    "def.+(.+//.+).+do"
   ],
   "coverage_options": {
     "treat_no_relevant_lines_as_covered": true
diff --git a/guides/migrations.md b/guides/migrations.md
index ba62bfd..e41f609 100644
--- a/guides/migrations.md
+++ b/guides/migrations.md
@@ -1,17 +1,121 @@
 # Migrations
 
-## Migrating from `Bonny.Controller` to `Bonny.ControllerV2`
-
-If you follow these steps, you should be able to successfully migrate your controllers to V2.
-The new concepts like `customize_crd/1` and RBAC rule definition are explained in the [controllers guide livebook](./controllers.livemd).
-
-- Change `use Bonny.Controller` to `use Bonny.ControllerV2`
-- If you have defined additional RBAC rules via `@rule {apiGroup, resources_list, verbs_list}`, replace each `@rule` attribute with a call to `rbac_rule` (`rbac_rule {apiGroup, resources_list, verbs_list}`).
-- If you have defined a custom version, e.g. `@version "v2alpha1"`, implement `customize_crd/1` and change the default version name.
-- If you have defined a custom group, e.g. `@group kewl.example.com`, implement `customize_crd/1` and change the group in your CRD.
-- If you have defined your controller to operate on cluster scope (i.e. `@scope :cluster`), implement `customize_crd/1` and change the scope in your CRD.
-- If you have defined custom names via `@names %{...}`, implement `customize_crd/1` and change the names in your CRD.
-- If you have defined additional printer columns, implement `customize_crd/1` and add them to the auto-generated default version in your CRD. There's a section in the [controllers guide livebook](./controllers.livemd) about this.
+## Migrating from 0.5 to a newer version
+
+This version of Bonny introduces a new controller module `Bonny.ControllerV2` and deprecates the old `Bonny.Controller`
+Follow these steps to migrate your operator to work with `Bonny.ControllerV2`.
+
+### Add API Versions to Application Configuration
+
+With this version of Bonny, API Versions are configured in the application config. This was done to bring
+bonny closer to the [Concepts of the Kubernetes API](https://kubernetes.io/docs/concepts/overview/kubernetes-api/#api-groups-and-versioning).
+
+> Versioning is done at the API level rather than at the resource or field level to ensure that the API presents a clear, consistent view of system resources and behavior, and to enable controlling access to end-of-life and/or experimental APIs.
+
+Assuming you already configured the API Group in config.exs, now also add a list of versions. This should be a list of Elixir Modules.
+**Note that these modules don't have to exist.**
+
+```elixir
+# config.exs
+
+config :bonny,
+
+  # ... other config ...
+
+  # Set the Kubernetes API group for this operator.
+  group: "your-operator.example.com",
+
+  # Set the Kubernetes API versions for this operator.
+  # This should be written in Elixir module form, e.g. YourOperator.API.V1 or YourOperator.API.V1Alpha1:
+  versions: [YourOperator.API.V1, YourOperator.API.V1Alpha1],
+
+  # ... other config ...
+```
+
+### Option1: Use `mix bonny.gen.controller`
+
+You can use the refurbished `mix bonny.gen.controller` to create a new
+version of your existing controller. Since with this version, controllers
+are not versioned anymore, the file will not be inside a `/v1/` folder.
+This means your existing controller won't be overwritten.
+
+### Option 2: Manual migration of your controllers
+
+#### Step 1 Create your API versions
+
+For each version you added to your application config above, create the corresponding folder:
+
+```bash
+mkdir lib/your_operator/api/v1 lib/your_operator/api/v1alpha1
+```
+
+Next, for each CRD your operator generates, add a module inside those folders.
+**Note that only one version of the same custom resource can be marked as the `hub`.**
+
+```elixir
+# lib/your_operator/api/v1/cron_tab.ex
+defmodule YourOperator.API.V1.CronTab do
+  use Bonny.API.Version,
+    hub: true
+
+  @impl Bonny.API.Version
+  def manifest(), do: default()
+end
+```
+
+```elixir
+# lib/your_operator/api/v1alpha1/cron_tab.ex
+defmodule YourOperator.API.V1Alpha1.CronTab do
+  use Bonny.API.Version
+
+  @impl Bonny.API.Version
+  def manifest(), do: defaults()
+end
+```
+
+#### Step 2: Additional Printer Columns
+
+Additional printer columns belong to CRD API versions, not to controllers. Therefore, if your
+controller defined additional printer columns, move those over to the verison you just created.
+Modify `manifest/0` for this purpose.
+
+```elixir
+# lib/your_operator/api/v1/cron_tab.ex
+defmodule YourOperator.API.V1Alpha1.CronTab do
+  use Bonny.API.Version
+
+  @impl Bonny.API.Version
+  def manifest() do
+    struct!(
+      defaults(),
+      additionalPrinterColumns: [
+        %{name: "foos", type: :integer, description: "Number of foos", jsonPath: ".spec.foos"}
+      ]
+    )
+  end
+end
+```
+
+#### Step 3: Update your Controllers
+
+- Change `use Bonny.Controller` to:
+
+  ```elixir
+  use Bonny.ControllerV2,
+    for_resource: Bonny.API.CRD.build_for_controller!(
+      names: Bonny.API.CRD.kind_to_names("CronTab"),
+      # scope: :Namespaced (is the default),
+      # group: "example.com" defaults to the API group in your config
+    ),
+  ```
+
+- If you have defined additional RBAC rules via `@rule {apiGroup, resources_list, verbs_list}`, replace each `@rule` attribute with a call to `rbac_rule({apiGroup, resources_list, verbs_list})`.
+- If you have defined custom names via `@names %{...}`, pass `names: %{singular: ...}` to `Bonny.API.CRD.build_for_controller!/1` or use the `Bonny.API.CRD.kind_to_names/1` helper.
+- If you have defined your controller to operate on cluster scope (i.e. `@scope :cluster`), pass `scope: cluster` to `Bonny.API.CRD.build_for_controller!/1`.
+- If you have defined a group that differs from the one from the application config, pass `group: "the-group.com"` to `Bonny.API.CRD.build_for_controller!/1`.
+- If you have defined a version that differs from the one form application config, pass `versions: [YourOperator.API.V1.CronTab]` to `Bonny.API.CRD.build_for_controller!/1`. (replace `V1` with the version and `CronTab` with the resource kind)
+
+The new concepts introduced with `Bonny.ControllerV2` are explained in the [controllers guide livebook](./controllers.livemd).
 
 After having migrated all controllers, re-generate your manifest using `mix bonny.gen.manifest`.
 
diff --git a/guides/mix_tasks.md b/guides/mix_tasks.md
new file mode 100644
index 0000000..ff11661
--- /dev/null
+++ b/guides/mix_tasks.md
@@ -0,0 +1,83 @@
+# Bonny Mix Tasks
+
+There are a number of mix tasks to help create a Kubernetes operator.
+
+`mix help | grep bonny`
+
+## Initialize your Operator (`mix bonny.init`)
+
+To add bonny to your existing elixir project (`mix new your_operator`), this tasks
+helps you to generate application configuration and initializing your test suite.
+
+Just run `mix bonny.init`, the task will ask you questions about your operator
+and initialize it.
+
+## Controller Generation (`mix bonny.gen.controller`)
+
+Guides you through the creation of a controller. Normally, a controller of an operator
+watches a custom resource defined by that same operator. This is the default flow through
+this mix task.
+However, you can also create a controller that watches and reacts on events of
+core resources or custom resources defined by another operator.
+
+See the [controllers guide](controllers.livemd) for further information about controllers.
+
+After the mix task ran through, open up your controller and add functionality for your
+resource's lifecycles:
+
+- Apply (or Add/Modify)
+- Delete
+- Reconcile; periodically called with each every instance of a CRD's resources
+
+Ideally you'd also open up the generated CRD versions and add OpenAPIV3 schema,
+additional printer columns, etc.
+
+Your operator can also have multiple controllers if you want to watch multiple
+resources in your operator. Just run the mix task again.
+
+### Generating a dockerfile
+
+The following command will generate a dockerfile _for your operator_. This will need to be pushed to a docker repository that your Kubernetes cluster can access.
+
+Again, this Dockerfile is for your operator, not for the pods your operator may deploy.
+
+You can skip this step when developing by running your operator _external_ to the cluster.
+
+```shell
+mix bonny.gen.dockerfile
+
+export BONNY_IMAGE=YOUR_IMAGE_NAME_HERE
+docker build -t ${BONNY_IMAGE} .
+docker push ${BONNY_IMAGE}:latest
+```
+
+### Generating Kubernetes manifest for operator
+
+This will generate the entire manifest for this operator including:
+
+- CRD manifests
+- RBAC
+- Service Account
+- Operator Deployment
+
+The operator manifest generator requires the `image` flag to be passed if you plan to deploy the operator in your cluster. This is the docker image URL of your operators docker image created by `mix bonny.gen.docker` above.
+
+```shell
+mix bonny.gen.manifest --image ${BONNY_IMAGE}
+```
+
+You may _omit_ the `--image` flag if you want to generate a manifest _without the deployment_ so that you can develop locally running the operator outside of the cluster.
+
+By default the manifest will generate the service account and deployment in the "default" namespace.
+
+_To set the namespace explicitly:_
+
+```shell
+mix bonny.gen.manifest --out - -n test
+```
+
+_Alternatively you can apply it directly to kubectl_:
+
+```shell
+mix bonny.gen.manifest --out - -n test | kubectl apply -f - -n test
+```
diff --git a/lib/bonny/api/crd.ex b/lib/bonny/api/crd.ex
index 1f60cc0..089a38f 100644
--- a/lib/bonny/api/crd.ex
+++ b/lib/bonny/api/crd.ex
@@ -87,7 +87,7 @@ defmodule Bonny.API.CRD do
   %Bonny.API.CRD{} struct. It's going to derive the CRD names from the
   controller's module name and takes the group from config.
   """
-  defmacro build_for_controller!(fields) do
+  defmacro build_for_controller!(fields \\ []) do
     kind =
       __CALLER__.module
       |> Module.split()
diff --git a/lib/bonny/config.ex b/lib/bonny/config.ex
index caddd7c..57a0f7d 100644
--- a/lib/bonny/config.ex
+++ b/lib/bonny/config.ex
@@ -18,7 +18,8 @@ defmodule Bonny.Config do
   def versions() do
     Application.get_env(:bonny, :versions) ||
       raise RuntimeError,
-        message: "You have to define a list of versions in your config.exs"
+        message:
+          "You have to define a list of versions in your application config under .bonny.versions"
   end
 
   @doc """
@@ -41,7 +42,7 @@ defmodule Bonny.Config do
   """
   @spec instance_name() :: binary
   def instance_name() do
-    System.get_env("BONNY_POD_NAME") || name()
+    Application.get_env(:bonny, :instance_name) || name()
   end
 
   @doc """
diff --git a/lib/bonny/controller_v2.ex b/lib/bonny/controller_v2.ex
index 78fb753..bb8232f 100644
--- a/lib/bonny/controller_v2.ex
+++ b/lib/bonny/controller_v2.ex
@@ -8,8 +8,17 @@ defmodule Bonny.ControllerV2 do
   Controllers allow for simple `add`, `modify`, `delete`, and `reconcile`
   handling of custom resources in the Kubernetes API.
 
-  This version of the controller lets you customize the resulting CRD before
-  you generate your manifest using `mix bonny.gen.manifest`.
+  ## Controlled Resource
+
+  You can pass the option `for_resource` to `use Bonny.ControllerV2`.
+  If the option is ommitted, Bonny assumes the controler controls a
+  custom resource tries to guess the custom resource's name
+  from the controller's module name.
+  If declared, `for_resource` can be:
+
+  * a string representing the kind of a resource
+  * a `%Bonny.API.CRD{}` struct
+  * a `%Bonny.API.ResourceEndpoint` struct.
   """
 
   @type action :: Bonny.Server.Watcher.action() | :reconcile
@@ -48,7 +57,7 @@ defmodule Bonny.ControllerV2 do
       unquote(__init_process__())
       unquote(__maybes__(opts[:skip_observed_generations]))
       unquote(__defs__())
-      unquote(__for_resource__(opts))
+      unquote(__for_resource__(opts[:for_resource]))
 
       defoverridable list_operation: 0, conn: 0
     end
@@ -155,18 +164,33 @@ defmodule Bonny.ControllerV2 do
     end
   end
 
-  def __for_resource__(opts) do
+  def __for_resource__(nil) do
+    for_resource = quote do: Bonny.API.CRD.build_for_controller!()
+    __for_resource__(for_resource)
+  end
+
+  def __for_resource__(for_resource) when is_binary(for_resource) do
+    for_resource =
+      quote do:
+              Bonny.API.CRD.build_for_controller!(
+                names: Bonny.API.CRD.kind_to_names(unquote(for_resource))
+              )
+
+    __for_resource__(for_resource)
+  end
+
+  def __for_resource__(for_resource) do
     quote do
       cond do
-        match?(%Bonny.API.ResourceEndpoint{}, unquote(opts)[:for_resource]) ->
-          def resource_endpoint(), do: unquote(opts)[:for_resource]
+        match?(%Bonny.API.ResourceEndpoint{}, unquote(for_resource)) ->
+          def resource_endpoint(), do: unquote(for_resource)
 
-        match?(%Bonny.API.CRD{}, unquote(opts)[:for_resource]) ->
+        match?(%Bonny.API.CRD{}, unquote(for_resource)) ->
           def resource_endpoint(),
-            do: Bonny.API.CRD.resource_endpoint(unquote(opts)[:for_resource])
+            do: Bonny.API.CRD.resource_endpoint(unquote(for_resource))
 
           def crd_manifest() do
-            unquote(opts)[:for_resource]
+            unquote(for_resource)
             |> Bonny.API.CRD.to_manifest()
             |> maybe_add_obseved_generation_status()
           end
diff --git a/lib/mix/bonny.ex b/lib/mix/bonny.ex
index d3d2cfb..777987e 100644
--- a/lib/mix/bonny.ex
+++ b/lib/mix/bonny.ex
@@ -33,6 +33,74 @@ defmodule Mix.Bonny do
     Mix.Generator.create_file(target, source)
   end
 
+  @spec render_template(binary(), binary(), keyword()) :: term()
+  def render_template(source, "-", bindings) do
+    EEx.eval_file(source, bindings) |> IO.puts()
+  end
+
+  def render_template(source, target, bindings),
+    do: Mix.Generator.copy_template(source, target, bindings)
+
+  @spec copy(binary, binary) :: term()
+  def copy(source, "-"), do: IO.puts(File.read!(source))
+  def copy(source, target), do: Mix.Generator.copy_file(source, target)
+
+  @doc """
+  Appends `append_content` to `target`. If `target` does not exist, a new
+  file with `new_file_content` is created.
+  """
+  @spec append_or_create_with(binary(), binary(), binary(), binary()) :: term()
+  def append_or_create_with(target, content_to_append, new_file_content, check) do
+    add_or_create_with(:append, target, content_to_append, new_file_content, check)
+  end
+
+  @doc """
+  Prepends `append_content` to `target`. If `target` does not exist, a new
+  file with `new_file_content` is created.
+  """
+  @spec prepend_or_create_with(binary(), binary(), binary(), binary()) :: term()
+  def prepend_or_create_with(target, content_to_prepend, new_file_content, check) do
+    add_or_create_with(:prepend, target, content_to_prepend, new_file_content, check)
+  end
+
+  @spec add_or_create_with(:append | :prepend, binary(), binary(), binary(), binary()) :: term()
+  def add_or_create_with(mode, target, content_to_add, new_file_content, check) do
+    cond do
+      !File.exists?(target) ->
+        Mix.Generator.create_file(target, new_file_content)
+        :ok
+
+      !(File.read!(target) =~ check) && mode == :append ->
+        add_content(mode, target, content_to_add)
+
+      true ->
+        :ok
+    end
+  end
+
+  defp add_content(:append, target, content_to_add) do
+    Owl.IO.puts([
+      Owl.Data.tag("* appending", :green),
+      "#{inspect(content_to_add)} to #{target}"
+    ])
+
+    {:ok, file} = File.open(target, [:append])
+    IO.binwrite(file, content_to_add)
+    File.close(file)
+    :ok
+  end
+
+  defp add_content(:prepend, target, content_to_add) do
+    Owl.IO.puts([
+      Owl.Data.tag("* prepending", :green),
+      "#{inspect(content_to_add)} to #{target}"
+    ])
+
+    file_content = File.read!(target)
+    File.write!(target, content_to_add <> file_content)
+    :ok
+  end
+
   @doc "Get the OTP app name"
   @spec app_name() :: binary
   def app_name() do
@@ -41,6 +109,14 @@ defmodule Mix.Bonny do
     |> Macro.camelize()
   end
 
+  @doc "Get the OTP app name with dashes"
+  @spec hyphenated_app_name() :: binary
+  def hyphenated_app_name() do
+    otp_app()
+    |> Atom.to_string()
+    |> String.replace("_", "-")
+  end
+
   @spec app_dir_name() :: binary
   def app_dir_name() do
     Macro.underscore(app_name())
@@ -55,7 +131,7 @@ defmodule Mix.Bonny do
   @spec no_umbrella! :: any
   def no_umbrella!() do
     if Mix.Project.umbrella?() do
-      Mix.raise("mix bonny.gen.* can only be run inside an application directory")
+      Mix.raise("mix bonny.init and bonny.gen.* can only be run inside an application directory")
     end
   end
 
@@ -63,4 +139,8 @@ defmodule Mix.Bonny do
   defp otp_app() do
     Mix.Project.config() |> Keyword.fetch!(:app)
   end
+
+  def error(message) do
+    message |> Owl.Data.tag(:red) |> Owl.IO.puts()
+  end
 end
diff --git a/lib/mix/tasks/bonny.gen.controller.ex b/lib/mix/tasks/bonny.gen.controller.ex
index 6d36299..bbd253c 100644
--- a/lib/mix/tasks/bonny.gen.controller.ex
+++ b/lib/mix/tasks/bonny.gen.controller.ex
@@ -71,6 +71,13 @@ defmodule Mix.Tasks.Bonny.Gen.Controller do
     |> Mix.Bonny.template()
     |> EEx.eval_file(binding)
     |> Mix.Bonny.render(test_out)
+
+    Owl.IO.puts([
+      Owl.Data.tag(
+        "Don't forget to add the controller to the list of controllers in your application config.",
+        :yellow
+      )
+    ])
   end
 
   # coveralls-ignore-start trivial code and we use stdout in tests
@@ -98,10 +105,14 @@ defmodule Mix.Tasks.Bonny.Gen.Controller do
   # coveralls-ignore-stop
 
   defp controller_name_valid?(controller_name) do
-    controller_name =~ ~r/^[A-Z]\w*(\.[A-Z]\w*)*$/
+    controller_name =~ ~r/^[A-Z][^\W_]*(\.[A-Z]\[^\W_]*)*$/
+  end
+
+  defp kind_valid?(kind) do
+    kind =~ ~r/^[A-Z][A-Za-z]*$/
   end
 
-  def get_input(input \\ []) do
+  def get_input(input) do
     cond do
       is_nil(input[:controller_name]) ->
         controller_name =
@@ -115,7 +126,7 @@ defmodule Mix.Tasks.Bonny.Gen.Controller do
         |> get_input()
 
       !controller_name_valid?(input[:controller_name]) ->
-        error(
+        Mix.Bonny.error(
           "The controller name you defined (#{input[:controller_name]}) is not a valid Elixir module name!"
         )
 
@@ -149,8 +160,10 @@ defmodule Mix.Tasks.Bonny.Gen.Controller do
         |> Keyword.put(:crd_name, crd_name || from_controller)
         |> get_input()
 
-      input[:with_crd] and !controller_name_valid?(input[:crd_name]) ->
-        error("The CRD name you defined (#{input[:crd_name]}) is not a valid kubernetes kind!")
+      input[:with_crd] and !kind_valid?(input[:crd_name]) ->
+        Mix.Bonny.error(
+          "The CRD name you defined (#{input[:crd_name]}) is not a valid kubernetes kind!"
+        )
 
         input
         |> Keyword.delete(:crd_name)
@@ -221,10 +234,6 @@ defmodule Mix.Tasks.Bonny.Gen.Controller do
     Keyword.merge([resource_endpoint: nil, crd_name: nil], init_values)
   end
 
-  defp error(message) do
-    message |> Owl.Data.tag(:red) |> Owl.IO.puts()
-  end
-
   # coveralls-ignore-start trivial code
   defp get_resource_endpoint("Secret"), do: ResourceEndpoint.new!("v1", "secrets")
   defp get_resource_endpoint("Service"), do: ResourceEndpoint.new!("v1", "services")
diff --git a/lib/mix/tasks/bonny.init.ex b/lib/mix/tasks/bonny.init.ex
new file mode 100644
index 0000000..511aaae
--- /dev/null
+++ b/lib/mix/tasks/bonny.init.ex
@@ -0,0 +1,211 @@
+defmodule Mix.Tasks.Bonny.Init do
+  @moduledoc """
+  Initialized an operator wiht bonny.
+
+  * Initializes application configuration
+  * Generates helper files for tests
+  """
+
+  use Mix.Task
+  @shortdoc "Initialized an operator wiht bonny."
+
+  # credo:disable-for-this-file Credo.Check.Refactor.CyclomaticComplexity
+
+  @default_resources %{
+    limits: %{cpu: "200m", memory: "200Mi"},
+    requests: %{cpu: "200m", memory: "200Mi"}
+  }
+  @rfc_1123_subdomain_check ~r/^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$/
+  @dns_1035_label_check ~r/^[a-z]([-a-z0-9]*[a-z0-9])?$/
+  def run(_args) do
+    input =
+      [
+        resources: @default_resources,
+        app_name: Mix.Bonny.app_name(),
+        app_dir_name: Mix.Bonny.app_dir_name()
+      ]
+      |> get_input()
+
+    create_discovery_file()
+    create_conn_file(input)
+    create_config_file(input)
+    import_bonny_config_in_main_config()
+    add_runtime_config()
+    add_dynamic_http_provder_to_test_helper()
+  end
+
+  def get_input(input \\ []) do
+    cond do
+      is_nil(input[:api_group]) ->
+        api_group =
+          Owl.IO.input(
+            label: "Please enter the API Group of your controller, e.g. your-operator.example.com"
+          )
+
+        input
+        |> Keyword.put(:api_group, api_group)
+        |> get_input()
+
+      !valid_rfc_1123_subdomain?(input[:api_group]) ->
+        Mix.Bonny.error(
+          "Invalid value: #{inspect(input[:api_group])}. A lowercase RFC 1123 subdomain must consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric character"
+        )
+
+        input
+        |> Keyword.delete(:api_group)
+        |> get_input()
+
+      is_nil(input[:version]) ->
+        version =
+          Owl.IO.input(
+            label:
+              "Please enter the API Version of your controller in Elixir module form, e.g. V1 or V1Alpha1"
+          )
+
+        next_input =
+          if valid_dns_1035_label?(String.downcase(version)) do
+            input
+            |> Keyword.put(
+              :version,
+              "#{Mix.Bonny.app_name()}.API.#{Mix.Bonny.ensure_module_name(version)}"
+            )
+          else
+            Mix.Bonny.error(
+              "Invalid value: #{inspect(input[:version])}. A DNS-1035 label must consist of lower case alphanumeric characters or '-', start with an alphabetic character, and end with an alphanumeric character"
+            )
+
+            input
+          end
+
+        get_input(next_input)
+
+      is_nil(input[:operator_name]) ->
+        default = Mix.Bonny.hyphenated_app_name()
+
+        operator_name =
+          Owl.IO.input(
+            label:
+              ~s(Please enter the name of your operator. It must only consist of only lowercase letters and hyphens. Defaults to "#{default}"),
+            optional: true
+          )
+
+        input
+        |> Keyword.put(:operator_name, operator_name || default)
+        |> get_input()
+
+      is_nil(input[:service_account_name]) ->
+        default = Mix.Bonny.hyphenated_app_name()
+
+        service_account_name =
+          Owl.IO.input(
+            label:
+              ~s(Please enter the name of the service account when run in kubernetes. It must only consist of only lowercase letters and hyphens. Defaults to "#{default}"),
+            optional: true
+          )
+
+        input
+        |> Keyword.put(:service_account_name, service_account_name || default)
+        |> get_input()
+
+      is_nil(input[:define_resources?]) ->
+        define_resources? =
+          Owl.IO.confirm(
+            message:
+              ~s(Would you like to customize the resources? Defaults to #{inspect(@default_resources)}")
+          )
+
+        input
+        |> Keyword.put(:define_resources?, define_resources?)
+        |> get_input()
+
+      input[:define_resources?] ->
+        cpu_request = Owl.IO.input(label: "Please enter the value for the CPU request, e.g. 200m")
+
+        mem_request =
+          Owl.IO.input(label: "Please enter the value for the memory request, e.g. 200Mi")
+
+        cpu_limit = Owl.IO.input(label: "Please enter the value for the CPU limit, e.g. 200m")
+        mem_limit = Owl.IO.input(label: "Please enter the value for the memory limit, e.g. 200Mi")
+
+        input
+        |> Keyword.put(:resources, %{
+          limits: %{cpu: cpu_limit, memory: mem_limit},
+          requests: %{cpu: cpu_request, memory: mem_request}
+        })
+        |> Keyword.put(:define_resources?, false)
+        |> get_input()
+
+      true ->
+        input
+    end
+  end
+
+  defp create_discovery_file() do
+    "init/discovery.json"
+    |> Mix.Bonny.template()
+    |> Mix.Bonny.copy("test/support/discovery.json")
+  end
+
+  defp create_config_file(input) do
+    "init/config.exs"
+    |> Mix.Bonny.template()
+    |> Mix.Bonny.render_template("config/bonny.exs", input)
+  end
+
+  defp create_conn_file(input) do
+    "init/k8s_conn.ex"
+    |> Mix.Bonny.template()
+    |> Mix.Bonny.render_template("lib/#{input[:app_dir_name]}/k8s_conn.ex", input)
+  end
+
+  defp import_bonny_config_in_main_config() do
+    check = ~s(import_config "bonny.exs")
+
+    append_conent = ~s(\n\n#{check})
+
+    new_file_content = """
+    import Config
+    #{append_conent}
+    """
+
+    Mix.Bonny.append_or_create_with("config/config.exs", append_conent, new_file_content, check)
+  end
+
+  defp add_runtime_config() do
+    check = """
+    # The name of this instance.
+    # Defaults to the operator name if env var not defined
+    config :bonny, instance_name: System.get_env("BONNY_POD_NAME")
+    """
+
+    append_conent = ~s(\n#{check})
+
+    new_file_content = """
+    import Config
+
+    #{check}
+    """
+
+    Mix.Bonny.append_or_create_with("config/runtime.exs", append_conent, new_file_content, check)
+  end
+
+  defp add_dynamic_http_provder_to_test_helper() do
+    check = "K8s.Client.DynamicHTTPProvider.start_link(nil)"
+    content_to_prepend = "#{check}\n"
+
+    new_file_content = """
+    #{content_to_prepend}
+    ExUnit.start()
+    """
+
+    Mix.Bonny.prepend_or_create_with(
+      "config/config.exs",
+      content_to_prepend,
+      new_file_content,
+      check
+    )
+  end
+
+  defp valid_rfc_1123_subdomain?(string), do: String.match?(string, @rfc_1123_subdomain_check)
+  defp valid_dns_1035_label?(string), do: String.match?(string, @dns_1035_label_check)
+end
diff --git a/mix.exs b/mix.exs
index b7797fe..f88e148 100644
--- a/mix.exs
+++ b/mix.exs
@@ -60,10 +60,10 @@ defmodule Bonny.MixProject do
       {:telemetry, "~> 1.0"},
       #  2.0 only supports Elixir >= 1.11
       {:ymlr, "~> 3.0"},
+      {:owl, "~> 0.5.1", runtime: false},
 
       # Dev deps
       {:mix_test_watch, "~> 1.1", only: :dev, runtime: false},
-      {:owl, "~> 0.5.1", only: [:dev, :test], runtime: false},
       {:dialyxir, "~> 1.2.0", only: [:dev, :test], runtime: false},
       # {:ex_doc, "~> 0.23", only: :dev},
       {:ex_doc, ">= 0.0.0", only: :dev, runtime: false},
diff --git a/priv/templates/bonny.gen/controller.ex b/priv/templates/bonny.gen/controller.ex
index d221123..d4446e9 100644
--- a/priv/templates/bonny.gen/controller.ex
+++ b/priv/templates/bonny.gen/controller.ex
@@ -4,20 +4,24 @@ defmodule <%= app_name %>.Controller.<%= controller_name %> do
 
   ## Controlled Resource
 
-  You have to pass the option `for_resource` to `use Bonny.ControllerV2`.
-  `for_resource` should be either a `%Bonny.API.CRD{}` struct or a
-  `%Bonny.API.ResourceEndpoint` struct.
+  You can pass the option `for_resource` to `use Bonny.ControllerV2`.
+  If the option is ommitted, Bonny assumes the controler controls a
+  custom resource tries to guess the custom resource's name
+  from the controller's module name.
+  If declared, `for_resource` can be:
+
+  * a string representing the kind of a resource
+  * a `%Bonny.API.CRD{}` struct
+  * a `%Bonny.API.ResourceEndpoint` struct.
 
   ### Examples
 
   ```
   use Bonny.ControllerV2,
     for_resource:
-      %Bonny.API.CRD{
-        group: "example.com",
+      Bonny.API.CRD.build_for_controller!(
         names: Bonny.API.CRD.kind_to_names("CronTab"),
-        versions: [MyController.API.CronTab.V1],
-      }
+      )
   ```
 
   ```
@@ -25,7 +29,7 @@ defmodule <%= app_name %>.Controller.<%= controller_name %> do
     for_resource:
       %Bonny.API.ResourceEndpoint{
         group: "apps",
-        version: "v1",
+        versions: "v1",
         resource_type: "deployments",
       }
   ```
diff --git a/priv/templates/bonny.gen/init/config.exs b/priv/templates/bonny.gen/init/config.exs
new file mode 100644
index 0000000..cadc8a4
--- /dev/null
+++ b/priv/templates/bonny.gen/init/config.exs
@@ -0,0 +1,37 @@
+
+import Config
+
+config :bonny,
+  # Add each Controller module for this operator to load here
+  # Defaults to none. This *must* be set.
+  controllers: [],
+
+  # Function to call to get a K8s.Conn object.
+  # The function should return a %K8s.Conn{} struct or a {:ok, %K8s.Conn{}} tuple
+  get_conn: {<%= assigns[:app_name] %>.K8sConn, :get, [config_env()]},
+
+  # The namespace to watch for Namespaced CRDs.
+  # Defaults to "default". `:all` for all namespaces
+  # Also configurable via environment variable `BONNY_POD_NAMESPACE`
+  namespace: "default",
+
+  # Set the Kubernetes API group for this operator.
+  group: "<%= assigns[:api_group] %>",
+
+  # Set the Kubernetes API versions for this operator.
+  # This should be written in Elixir module form, e.g. YourOperator.API.V1 or YourOperator.API.V1Alpha1:
+  versions: [<%= assigns[:version] %>],
+
+  # Name must only consist of only lowercase letters and hyphens.
+  # Defaults to hyphenated mix app name
+  operator_name: "<%= assigns[:operator_name] %>",
+
+  # Name must only consist of only lowercase letters and hyphens.
+  # Defaults to hyphenated mix app name
+  service_account_name: "<%= assigns[:service_account_name] %>",
+
+  # Labels to apply to the operator's resources.
+  labels: %{},
+
+  # Operator deployment resources. These are the defaults.
+  resources: <%= inspect(assigns[:resources]) %>
diff --git a/priv/templates/bonny.gen/init/discovery.json b/priv/templates/bonny.gen/init/discovery.json
new file mode 100644
index 0000000..be5397c
--- /dev/null
+++ b/priv/templates/bonny.gen/init/discovery.json
@@ -0,0 +1,116 @@
+{
+  "v1": [
+    {
+      "kind": "Namespace",
+      "name": "namespaces",
+      "namespaced": false,
+      "verbs": ["create", "delete", "get", "list", "patch", "update", "watch"]
+    },
+    {
+      "kind": "ConfigMap",
+      "name": "configmaps",
+      "namespaced": true,
+      "verbs": [
+        "create",
+        "delete",
+        "deletecollection",
+        "get",
+        "list",
+        "patch",
+        "update",
+        "watch"
+      ]
+    },
+    {
+      "kind": "Service",
+      "name": "services",
+      "namespaced": true,
+      "verbs": [
+        "create",
+        "delete",
+        "deletecollection",
+        "get",
+        "list",
+        "patch",
+        "update",
+        "watch"
+      ]
+    },
+    {
+      "kind": "Pod",
+      "name": "pods",
+      "namespaced": true,
+      "verbs": [
+        "create",
+        "delete",
+        "deletecollection",
+        "get",
+        "list",
+        "patch",
+        "update",
+        "watch"
+      ]
+    }
+  ],
+  "apps/v1": [
+    {
+      "kind": "DaemonSet",
+      "name": "daemonsets",
+      "namespaced": true,
+      "verbs": [
+        "create",
+        "delete",
+        "deletecollection",
+        "get",
+        "list",
+        "patch",
+        "update",
+        "watch"
+      ]
+    },
+    {
+      "kind": "Deployment",
+      "name": "deployments",
+      "namespaced": true,
+      "verbs": [
+        "create",
+        "delete",
+        "deletecollection",
+        "get",
+        "list",
+        "patch",
+        "update",
+        "watch"
+      ]
+    },
+    {
+      "kind": "Deployment",
+      "name": "deployments/status",
+      "namespaced": true,
+      "verbs": ["get", "patch", "update"]
+    },
+    {
+      "kind": "StatefulSet",
+      "name": "statefulsets",
+      "namespaced": true,
+      "verbs": [
+        "create",
+        "delete",
+        "deletecollection",
+        "get",
+        "list",
+        "patch",
+        "update",
+        "watch"
+      ]
+    }
+  ],
+  "events.k8s.io/v1": [
+    {
+      "kind": "Event",
+      "name": "events",
+      "namespaced": false,
+      "verbs": ["create", "get", "list", "watch"]
+    }
+  ]
+}
diff --git a/priv/templates/bonny.gen/init/k8s_conn.ex b/priv/templates/bonny.gen/init/k8s_conn.ex
new file mode 100644
index 0000000..7746aaf
--- /dev/null
+++ b/priv/templates/bonny.gen/init/k8s_conn.ex
@@ -0,0 +1,21 @@
+defmodule <%= assigns[:app_name] %>.K8sConn do
+  @moduledoc """
+  Initializes the %K8s.Conn{} struct depending on the mix environment. To be used in config.exs (bonny.exs):
+
+  ```
+  # Function to call to get a K8s.Conn object.
+  # The function should return a %K8s.Conn{} struct or a {:ok, %K8s.Conn{}} tuple
+  get_conn: {<%= assigns[:app_name] %>.K8sConn, :get, [config_env()]},
+  ```
+  """
+
+  @spec get(atom()) :: K8s.Conn.t()
+  def get(:dev), do: K8s.Conn.from_file("~/.kube/config", context: "docker-desktop")
+  def get(:test), do:
+    %K8s.Conn{
+      discovery_driver: K8s.Discovery.Driver.File,
+      discovery_opts: [config: "test/support/discovery.json"],
+      http_provider: K8s.Client.DynamicHTTPProvider
+    }
+  def get(_), do: K8s.Conn.from_service_account()
+end


Test patch:
diff --git a/test/bonny/controller_v2_test.exs b/test/bonny/controller_v2_test.exs
index ac1c03a..b17fadb 100644
--- a/test/bonny/controller_v2_test.exs
+++ b/test/bonny/controller_v2_test.exs
@@ -10,6 +10,7 @@ defmodule Bonny.ControllerV2Test do
     defmodule V1Beta1 do
       use Version
 
+      @impl true
       def manifest(), do: defaults("v1beta1")
     end
 
diff --git a/test/mix/tasks/bonny.gen.controller_test.exs b/test/mix/tasks/bonny.gen.controller_test.exs
index f51a009..67c2650 100644
--- a/test/mix/tasks/bonny.gen.controller_test.exs
+++ b/test/mix/tasks/bonny.gen.controller_test.exs
@@ -15,6 +15,7 @@ defmodule Mix.Tasks.Bonny.Gen.ControllerTest do
     [
       crd_no_args: ~w(McController y Memcached) |> input_args(),
       crd_arg_controller: ~w(y Memcached) |> input_args(),
+      crd_arg_controller_inv: ~w(y Mem_Cached Memcached) |> input_args(),
       crd_arg_controller_version: ~w(y Memcached) |> input_args(),
       deployment_no_args: ["DeplController", "n", @choice_resource_deployment] |> input_args(),
       deployment_arg_controller: ["n", @choice_resource_deployment] |> input_args(),
@@ -53,6 +54,26 @@ defmodule Mix.Tasks.Bonny.Gen.ControllerTest do
       assert output =~ "defmodule Bonny.Test.API.V1.Memcached do"
     end
 
+    test "Asks for controller name again if it is invalid.",
+         %{crd_no_args: input} do
+      output =
+        capture_io([input: "Invalid_Controller_Name\n" <> input], fn ->
+          Controller.run(["--out", "-"])
+        end)
+
+      assert output =~ "not a valid Elixir module name!"
+    end
+
+    test "Asks for CRD name again if it is invalid.",
+         %{crd_arg_controller_inv: input} do
+      output =
+        capture_io([input: input], fn ->
+          Controller.run(["--out", "-", "McController"])
+        end)
+
+      assert output =~ "is not a valid kubernetes kind!"
+    end
+
     test "generates a CRD controller and a Version module module when controller name is passed.",
          %{
            crd_arg_controller: input
@@ -141,7 +162,11 @@ defmodule Mix.Tasks.Bonny.Gen.ControllerTest do
       _modules =
         output
         |> String.replace(~r/^.*?defmodule/s, "defmodule")
-        |> Code.compile_string()
+        |> String.replace(
+          ~r/.*Don't forget to add the controller to the list of controllers in your application config.*/,
+          ""
+        )
+        |> Code.eval_string()
     end
 
     test "generated modules compile (deployment)", %{deployment_no_args: input} do
@@ -153,7 +178,11 @@ defmodule Mix.Tasks.Bonny.Gen.ControllerTest do
       _modules =
         output
         |> String.replace(~r/^.*?defmodule/s, "defmodule")
-        |> Code.compile_string()
+        |> String.replace(
+          ~r/.*Don't forget to add the controller to the list of controllers in your application config.*/,
+          ""
+        )
+        |> Code.eval_string()
     end
 
     test "generated modules compile (sealed secret)", %{sealed_secret_no_args: input} do
@@ -165,6 +194,10 @@ defmodule Mix.Tasks.Bonny.Gen.ControllerTest do
       _modules =
         output
         |> String.replace(~r/^.*?defmodule/s, "defmodule")
+        |> String.replace(
+          ~r/.*Don't forget to add the controller to the list of controllers in your application config.*/,
+          ""
+        )
         |> Code.compile_string()
     end
 
@@ -193,5 +226,14 @@ defmodule Mix.Tasks.Bonny.Gen.ControllerTest do
 
       assert output =~ "defmodule Bonny.Controller.McControllerTest do"
     end
+
+    test "prints the help message" do
+      output =
+        capture_io(fn ->
+          catch_exit(Controller.run(["-h"]))
+        end)
+
+      assert output =~ "mix bonny.gen.controller"
+    end
   end
 end
diff --git a/test/support/controllers/test_resource_v32_controller.ex b/test/support/controllers/test_resource_v32_controller.ex
index ac3d676..4c61749 100644
--- a/test/support/controllers/test_resource_v32_controller.ex
+++ b/test/support/controllers/test_resource_v32_controller.ex
@@ -7,7 +7,7 @@ defmodule TestResourceV32Controller do
   require CRD
 
   use Bonny.ControllerV2,
-    for_resource: CRD.build_for_controller!(names: Bonny.API.CRD.kind_to_names("TestResourceV3")),
+    for_resource: "TestResourceV3",
     skip_observed_generations: true
 
   @impl Bonny.ControllerV2
diff --git a/test/support/controllers/test_resource_v3_controller.ex b/test/support/controllers/test_resource_v3_controller.ex
index 74510db..9e5e3b8 100644
--- a/test/support/controllers/test_resource_v3_controller.ex
+++ b/test/support/controllers/test_resource_v3_controller.ex
@@ -7,7 +7,6 @@ defmodule TestResourceV3Controller do
   require CRD
 
   use Bonny.ControllerV2,
-    for_resource: CRD.build_for_controller!(names: CRD.kind_to_names("TestResourceV3")),
     skip_observed_generations: true
 
   @impl Bonny.ControllerV2