• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mruoss / kompost / 75ee2c8e4e8ca08f493ceeb35be5738217e9d890

13 Jun 2024 06:08AM UTC coverage: 54.25% (-6.5%) from 60.759%
75ee2c8e4e8ca08f493ceeb35be5738217e9d890

push

github

mruoss
lockfile maint

300 of 553 relevant lines covered (54.25%)

95.36 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.1
/lib/kompost/kompo/postgres/controller/instance_controller.ex
1
defmodule Kompost.Kompo.Postgres.Controller.InstanceController do
2
  use Bonny.ControllerV2
3

4
  require Logger
5

6
  alias Kompost.Kompo.Postgres.Instance
7

8
  alias Kompost.Tools.NamespaceAccess
9

10
  step Bonny.Pluggable.SkipObservedGenerations
11

12
  step Bonny.Pluggable.Finalizer,
13
    id: "kompost.chuge.li/databases",
14
    impl: &__MODULE__.check_for_depending_databases/1,
15
    add_to_resource: true,
16
    log_level: :debug
17

18
  step Kompost.Pluggable.InitConditions, conditions: ["Credentials", "Connected", "Privileged"]
19
  step :handle_event
20

21
  @impl true
22
  def rbac_rules() do
×
23
    [to_rbac_rule({"", "secrets", ["get", "list"]})]
24
  end
25

26
  @spec handle_event(Bonny.Axn.t(), Keyword.t()) :: Bonny.Axn.t()
27
  def handle_event(%Bonny.Axn{action: action} = axn, _opts)
28
      when action in [:add, :modify, :reconcile] do
29
    id = instance_id(axn.resource)
13✔
30
    allowed_ns = allowed_namespaces(axn.resource)
13✔
31

32
    with {:cred, {:ok, connection_args}} <- {:cred, get_connection_args(axn.resource, axn.conn)},
13✔
33
         axn <- set_condition(axn, "Credentials", true),
12✔
34
         {:conn, axn, {:ok, conn}} <-
12✔
35
           {:conn, axn, Instance.connect(id, connection_args, allowed_ns)},
36
         axn <- set_condition(axn, "Connected", true, "Connection to database was established"),
10✔
37
         {:privileges, axn, :ok} <- {:privileges, axn, Instance.check_privileges(conn)},
10✔
38
         axn <-
10✔
39
           set_condition(axn, "Privileged", true, "The conneted user has the required privileges") do
40
      success_event(axn)
10✔
41
    else
42
      {:cred, {:error, error}} ->
43
        Logger.warning("Error when trying to fetch password.", error: error)
1✔
44

45
        axn
46
        |> failure_event(message: Exception.message(error))
47
        |> set_condition("Credentials", false, Exception.message(error))
1✔
48

49
      {:conn, axn, {:error, error}} ->
50
        message = if is_exception(error), do: Exception.message(error), else: inspect(error)
2✔
51
        Logger.warning("#{axn.action} failed. #{message}")
2✔
52

53
        axn
54
        |> failure_event(message: message)
55
        |> set_condition("Connected", false, message)
2✔
56

57
      {:privileges, axn, {:error, message}} ->
58
        Logger.warning("#{axn.action} failed. #{message}")
×
59

60
        axn
61
        |> failure_event(message: message)
62
        |> set_condition("Privileged", false, message)
×
63
    end
64
  end
65

66
  def handle_event(%Bonny.Axn{action: :delete} = axn, _opts) do
67
    :ok =
12✔
68
      axn.resource
12✔
69
      |> instance_id()
70
      |> Instance.disconnect()
71

72
    success_event(axn)
12✔
73
  end
74

75
  @spec get_connection_args(map(), K8s.Conn.t()) ::
76
          {:ok, [Postgrex.start_option()]} | {:error, K8s.Client.Runner.Base.error_t()}
77
  defp get_connection_args(%{"spec" => %{"plainPassword" => _} = spec}, _conn) do
78
    cacerts =
12✔
79
      case spec["ssl"]["ca"] do
80
        nil ->
81
          :public_key.cacerts_get()
12✔
82

83
        cacert ->
84
          cacert
85
          |> :public_key.pem_decode()
86
          |> Enum.map(fn {_, der, _} -> der end)
×
87
      end
88

89
    {:ok,
90
     [
91
       hostname: spec["hostname"],
92
       port: spec["port"],
93
       username: spec["username"],
94
       password: spec["plainPassword"],
95
       database: "postgres",
96
       ssl: spec["ssl"]["enabled"] || false,
12✔
97
       ssl_opts: [
98
         verify: (spec["ssl"]["verify"] || "verify_none") |> String.to_atom(),
12✔
99
         cacerts: cacerts,
100
         server_name_indication: String.to_charlist(spec["hostname"])
101
       ]
102
     ]}
103
  end
104

105
  defp get_connection_args(instance, conn) do
106
    %{
107
      "spec" => %{"passwordSecretRef" => %{"name" => secret_name, "key" => key}}
108
    } = instance
6✔
109

110
    with {:ok, secret} <-
6✔
111
           K8s.Client.get("v1", "Secret",
112
             name: secret_name,
113
             namespace:
114
               instance["metadata"]["namespace"] ||
5✔
115
                 Application.fetch_env!(:kompost, :operator_namespace)
1✔
116
           )
117
           |> K8s.Client.put_conn(conn)
118
           |> K8s.Client.run() do
119
      password = Base.decode64!(secret["data"][key])
5✔
120
      instance = put_in(instance, ~w(spec plainPassword), password)
5✔
121
      get_connection_args(instance, conn)
5✔
122
    end
123
  end
124

125
  @doc """
126
  A finalizer preventing the deletion of an instance if there are database
127
  resources in the cluster which still depend on it.
128
  """
129
  @spec check_for_depending_databases(Bonny.Axn.t()) ::
130
          {:ok, Bonny.Axn.t()} | {:error, Bonny.Axn.t()}
131
  def check_for_depending_databases(%Bonny.Axn{resource: resource, conn: conn} = axn) do
132
    {:ok, result} =
12✔
133
      K8s.Client.list("kompost.chuge.li/v1alpha1", "PostgresDatabase", namespace: :all)
134
      |> K8s.Client.put_conn(conn)
135
      |> K8s.Client.run()
136

137
    if Enum.any?(
12✔
138
         result["items"],
139
         &(is_nil(&1["metadata"]["deletionTimestamp"]) &&
11✔
140
             &1["spec"]["instanceRef"]["name"] == resource["metadata"]["name"] and
51✔
141
             &1["spec"]["instanceRef"]["namespace"] == resource["metadata"]["namespace"])
×
142
       ) do
143
      {:error, axn}
144
    else
145
      {:ok, axn}
146
    end
147
  end
148

149
  @spec instance_id(resource :: map()) :: Instance.id()
150
  defp instance_id(%{"kind" => "PostgresInstance", "metadata" => metadata}),
15✔
151
    do: {metadata["namespace"], metadata["name"]}
152

153
  defp instance_id(%{"kind" => "PostgresClusterInstance", "metadata" => metadata}),
10✔
154
    do: {:cluster, metadata["name"]}
155

156
  @spec allowed_namespaces(map()) :: NamespaceAccess.allowed_namespaces()
157
  defp(allowed_namespaces(%{"kind" => "PostgresInstance"} = resource)) do
8✔
158
    [~r/^#{resource["metadata"]["namespace"]}$/]
8✔
159
  end
160

161
  defp allowed_namespaces(%{"kind" => "PostgresClusterInstance"} = resource) do
162
    NamespaceAccess.allowed_namespaces!(resource)
5✔
163
  end
164
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc