Repository: coryodaniel/bonny (elixir)

Issue:
Automatically inject ownerReferences
Inject [`metadata.ownerReferences`](https://kubernetes.io/docs/concepts/workloads/controllers/garbage-collection/) into resources.

I'm not positive the [original idea](#original-idea) below will work. Middleware is per cluster and it would need a per resource data to inject ownerReference.

Definitely worth looking into, but a simple solution for now could be to add an `add_owner_references/2` function to the Controller behavior.

```elixir
def added(todo = %{}) do
  %K8s.Operation{} # make an operation
  |> add_owner_references(todo) # attached `todo` crd references
  |> K8s.run(conn)
end
```

```yaml
apiVersion: v1
kind: Pod
metadata:
  ...
  ownerReferences:
  - apiVersion: apps/v1
    controller: true
    blockOwnerDeletion: true
    kind: ReplicaSet
    name: my-repset
    uid: d9607e19-f88f-11e6-a518-42010a800195
  ...
```
---

### original idea

This will require
* [x] [`K8s.Middleware`](https://github.com/coryodaniel/k8s/issues/42) to modify all JSON paylaods during a lifecycle event
* [ ] Ensure lifecycle events are dispatched in their own process
* [ ] Store custom resource's information by process ID so that it can be fetched by middleware
* [ ] default implementation to make the delete lifecycle a no-op as deletion should happen via k8s/ownerReferences garbage collection

Related #79

Pull request description:
Add owner reference
closes #49

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 803be9e..ce21d39 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -11,6 +11,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 
 <!-- ### Added | Changed | Deprecated | Removed | Fixed | Security -->
 
+### Added
+
+- `Bonny.Controller.add_owner_reference/3` used to add the owner reference to resources created by the controller.
+
+<!-- No new entries below this line! -->
+
 ## [0.5.2] - 2022-08-31
 
 ### Updated
diff --git a/lib/bonny/controller.ex b/lib/bonny/controller.ex
index 9b744c0..07ba3d0 100644
--- a/lib/bonny/controller.ex
+++ b/lib/bonny/controller.ex
@@ -83,6 +83,8 @@ defmodule Bonny.Controller do
       @impl Bonny.Controller
       defdelegate conn(), to: Bonny.Config
 
+      defdelegate add_owner_reference(resource, owner, opts \\ []), to: Bonny.Controller
+
       defoverridable list_operation: 0, conn: 0
     end
   end
@@ -153,4 +155,46 @@ defmodule Bonny.Controller do
       _ -> K8s.Client.list(api_version, kind)
     end
   end
+
+  @spec add_owner_reference(map(), map(), keyword(boolean)) :: map()
+  def add_owner_reference(resource, owner, opts \\ [])
+
+  def add_owner_reference(%{"metadata" => _} = resource, owner, opts) do
+    owner_ref = owner_reference(owner, opts)
+
+    put_in(
+      resource,
+      [
+        "metadata",
+        Access.key("ownerReferences", []),
+        K8s.Resource.NamedList.access(owner_ref["name"])
+      ],
+      owner_ref
+    )
+  end
+
+  def add_owner_reference(%{metadata: _} = resource, owner, opts) do
+    owner_ref = owner_reference(owner, opts)
+
+    put_in(
+      resource,
+      [
+        :metadata,
+        Access.key(:ownerReferences, []),
+        K8s.Resource.NamedList.access(owner_ref["name"])
+      ],
+      owner_ref
+    )
+  end
+
+  defp owner_reference(resource, opts) do
+    %{
+      "apiVersion" => get_in(resource, ["apiVersion"]),
+      "kind" => get_in(resource, ["kind"]),
+      "name" => get_in(resource, ["metadata", "name"]),
+      "uid" => get_in(resource, ["metadata", "uid"]),
+      "blockOwnerDeletion" => Keyword.get(opts, :block_owner_deletion, false),
+      "controller" => true
+    }
+  end
 end


Test patch:
diff --git a/test/bonny/controller_test.exs b/test/bonny/controller_test.exs
index a4677fd..a45922a 100644
--- a/test/bonny/controller_test.exs
+++ b/test/bonny/controller_test.exs
@@ -101,6 +101,90 @@ defmodule Bonny.ControllerTest do
     end
   end
 
+  describe "add_owner_reference/3" do
+    setup do
+      resource = %{
+        "apiVersion" => "example.com/v1",
+        "kind" => "Cog",
+        "metadata" => %{
+          "name" => "some-cog",
+          "namespace" => "default"
+        }
+      }
+
+      resource_with_atoms = %{
+        apiVersion: "example.com/v1",
+        kind: "Cog",
+        metadata: %{
+          name: "some-cog",
+          namespace: "default"
+        }
+      }
+
+      owner = %{
+        "apiVersion" => "example.com/v1",
+        "kind" => "Widget",
+        "metadata" => %{
+          "name" => "some-widget",
+          "namespace" => "default",
+          "uid" => "d9607e19-f88f-11e6-a518-42010a800195"
+        }
+      }
+
+      [resource: resource, resource_with_atoms: resource_with_atoms, owner: owner]
+    end
+
+    test "adds the reference", %{resource: resource, owner: owner} do
+      resource_with_ownerref = Bonny.Controller.add_owner_reference(resource, owner)
+
+      assert hd(resource_with_ownerref["metadata"]["ownerReferences"]) == %{
+               "apiVersion" => "example.com/v1",
+               "kind" => "Widget",
+               "blockOwnerDeletion" => false,
+               "controller" => true,
+               "name" => "some-widget",
+               "uid" => "d9607e19-f88f-11e6-a518-42010a800195"
+             }
+    end
+
+    test "adds the reference to list of existing references", %{resource: resource, owner: owner} do
+      resource_with_ownerref =
+        resource
+        |> put_in(["metadata", "ownerReferences"], [
+          %{"name" => "existing-ref", "uid" => "foobar"}
+        ])
+        |> Bonny.Controller.add_owner_reference(owner)
+
+      assert length(resource_with_ownerref["metadata"]["ownerReferences"]) == 2
+    end
+
+    test "adds the reference for resource with atoms", %{
+      resource_with_atoms: resource,
+      owner: owner
+    } do
+      resource_with_ownerref = Bonny.Controller.add_owner_reference(resource, owner)
+
+      assert hd(resource_with_ownerref[:metadata][:ownerReferences]) == %{
+               "apiVersion" => "example.com/v1",
+               "kind" => "Widget",
+               "blockOwnerDeletion" => false,
+               "controller" => true,
+               "name" => "some-widget",
+               "uid" => "d9607e19-f88f-11e6-a518-42010a800195"
+             }
+    end
+
+    test "sets blockOnwerDeletion", %{resource: resource, owner: owner} do
+      resource_with_ownerref =
+        Bonny.Controller.add_owner_reference(resource, owner, block_owner_deletion: true)
+
+      assert resource_with_ownerref
+             |> get_in(["metadata", "ownerReferences"])
+             |> hd()
+             |> Map.get("blockOwnerDeletion") == true
+    end
+  end
+
   test "builds RBAC rules when set" do
     assert V2.Whizbang.rules() == [
              %{apiGroups: ["apiextensions.k8s.io"], resources: ["bar"], verbs: ["*"]},