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

mruoss / kompost / 1758b59c883827cd17aa56b7b7f1e515212c1f13

29 Apr 2024 07:52PM UTC coverage: 60.398% (-0.7%) from 61.121%
1758b59c883827cd17aa56b7b7f1e515212c1f13

push

github

mruoss
update and remove insecure_skip_tls_verify

0 of 2 new or added lines in 1 file covered. (0.0%)

4 existing lines in 2 files now uncovered.

334 of 553 relevant lines covered (60.4%)

7.29 hits per line

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

84.81
/lib/kompost/kompo/postgres/controller/database_controller.ex
1
defmodule Kompost.Kompo.Postgres.Controller.DatabaseController do
2
  use Bonny.ControllerV2
3

4
  require Logger
5

6
  alias Kompost.Kompo.Postgres.Database
7
  alias Kompost.Kompo.Postgres.Database.Params
8
  alias Kompost.Kompo.Postgres.Instance
9
  alias Kompost.Kompo.Postgres.Privileges
10
  alias Kompost.Kompo.Postgres.User
11

12
  alias Kompost.Tools.NamespaceAccess
13
  alias Kompost.Tools.Password
14

15
  import YamlElixir.Sigil
16

17
  step Bonny.Pluggable.SkipObservedGenerations
18

19
  step Kompost.Pluggable.InitConditions, conditions: ["Connection", "AppUser", "InspectorUser"]
20

21
  step Bonny.Pluggable.Finalizer,
22
    id: "kompost.chuge.li/delete-resources",
23
    impl: &__MODULE__.delete_resources/1,
24
    add_to_resource: &__MODULE__.add_finalizer?/1,
25
    log_level: :debug
26

27
  step :handle_event
28

29
  @impl true
30
  def rbac_rules() do
×
31
    [to_rbac_rule({"", "secrets", ["*"]})]
32
  end
33

34
  @spec handle_event(Bonny.Axn.t(), Keyword.t()) :: Bonny.Axn.t()
35
  def handle_event(%Bonny.Axn{action: action} = axn, _opts)
36
      when action in [:add, :modify, :reconcile] do
37
    resource = axn.resource
9✔
38
    namespace = resource["metadata"]["namespace"]
9✔
39

40
    db_name =
9✔
41
      Database.name(resource,
42
        strategy: resource["spec"]["databaseNamingStrategy"] || "prefix_namespace"
9✔
43
      )
44

45
    db_params = Params.new!(resource["spec"]["params"] || %{})
9✔
46
    instance = resource |> instance_id() |> Instance.lookup()
9✔
47

48
    with {:instance, [{conn, conn_args, allowed_namespaces}]} <- {:instance, instance},
9✔
49
         axn <-
8✔
50
           set_condition(
51
             axn,
52
             "Connection",
53
             true,
54
             "Connected to the referenced PostgreSQL instance."
55
           ),
56
         {:can_access, axn, true} <-
8✔
57
           {:can_access, axn, NamespaceAccess.can_access?(namespace, allowed_namespaces)},
58
         {:database, axn, :ok} <- {:database, axn, Database.apply(db_name, db_params, conn)},
7✔
59
         axn <-
7✔
60
           set_condition(
61
             axn,
62
             "Database",
63
             true,
64
             ~s(The database "#{db_name}" was created on the PostgreSQL instance)
7✔
65
           ),
66
         axn <- Bonny.Axn.update_status(axn, &Map.put(&1, "sql_db_name", db_name)),
7✔
67
         {:app_user, {:ok, axn}} <-
7✔
68
           {:app_user, apply_user("app", :read_write, axn, conn, conn_args, db_name)},
69
         axn <-
7✔
70
           set_condition(
71
             axn,
72
             "AppUser",
73
             true,
74
             "Application user was created successfully."
75
           ),
76
         {:inspector_user, {:ok, axn}} <-
7✔
77
           {:inspector_user, apply_user("inspector", :read_only, axn, conn, conn_args, db_name)},
78
         axn <-
7✔
79
           set_condition(
80
             axn,
81
             "InspectorUser",
82
             true,
83
             "Inspector user was created successfully."
84
           ) do
85
      success_event(axn)
7✔
86
    else
87
      {:instance, []} ->
88
        message = "The referenced PostgreSQL instance was not found."
1✔
89
        Logger.warning("#{axn.action} failed. #{message}")
1✔
90

91
        axn
92
        |> failure_event(message: message)
93
        |> set_condition("Connection", false, message)
1✔
94

95
      {:database, axn, {:error, message}} ->
96
        Logger.warning("#{axn.action} failed. #{message}")
×
97

98
        axn
99
        |> failure_event(message: message)
100
        |> set_condition("Database", false, message)
×
101

102
      {:can_access, axn, false} ->
103
        message =
1✔
104
          ~s(The referenced PostgresClusterInstance cannot be accesed. Check the annotation "kompost.chuge.li/allowed-namespaces" on the PostgresClusterInstance.)
105

106
        Logger.warning("#{axn.action} failed. #{message}")
1✔
107

108
        axn
109
        |> failure_event(message: message)
110
        |> set_condition("ClusterInstanceAccess", false, message)
1✔
111

112
      {:app_user, {:error, axn, message}} ->
113
        Logger.warning("#{axn.action} failed. #{message}")
×
114

115
        axn
116
        |> failure_event(message: message)
117
        |> set_condition("AppUser", false, message)
×
118

119
      {:inspector_user, {:error, axn, message}} ->
120
        Logger.warning("#{axn.action} failed. #{message}")
×
121

122
        axn
123
        |> failure_event(message: message)
124
        |> set_condition("InspectorUser", false, message)
×
125
    end
126
  end
127

128
  # See `delete_resources/1`
129
  def handle_event(%Bonny.Axn{action: :delete} = axn, _opts) do
130
    success_event(axn)
7✔
131
  end
132

133
  @doc """
134
  Finalizer preventing the deletion of the database resource until underlying
135
  resources on the postgres instance are cleaned up.
136
  """
137
  @spec delete_resources(Bonny.Axn.t()) :: {:ok, Bonny.Axn.t()} | {:error, Bonny.Axn.t()}
138
  def delete_resources(axn) do
139
    resource = axn.resource
8✔
140

141
    db_name =
8✔
142
      Database.name(resource,
143
        strategy: resource["spec"]["databaseNamingStrategy"] || "prefix_namespace"
8✔
144
      )
145

146
    users = resource["status"]["users"]
8✔
147
    instance = resource |> instance_id() |> Instance.lookup()
8✔
148

149
    with {:instance, [{conn, _conn_args, _allowed_namespaces}]} <- {:instance, instance},
8✔
150
         {:users, axn, :ok} <- {:users, axn, drop_users(users, db_name, conn)},
8✔
151
         {:database, axn, :ok} <- {:database, axn, Database.drop(db_name, conn)} do
8✔
152
      {:ok, axn}
153
    else
154
      {:instance, []} ->
UNCOV
155
        Logger.warning(
×
156
          "The referenced PostgreSQL instance was not found. But we consider the Database removed."
157
        )
158

159
        {:ok, axn}
160

161
      {:users, axn, {:error, message}} ->
162
        Logger.warning("Failed to finalize. #{message}")
×
163
        failure_event(axn, message: message)
×
164
        {:error, axn}
165

166
      {:database, axn, {:error, message}} ->
167
        Logger.warning("Failed to finalize. #{message}")
2✔
168
        failure_event(axn, message: message)
2✔
169
        {:error, axn}
170
    end
171
  end
172

173
  @spec apply_user_secret(
174
          axn :: Bonny.Axn.t(),
175
          secret_name :: binary(),
176
          user_env :: map(),
177
          conn_args :: Keyword.t()
178
        ) :: {:ok, Bonny.Axn.t()}
179
  defp apply_user_secret(axn, secret_name, user_env, conn_args) do
180
    data =
14✔
181
      Map.merge(user_env, %{
182
        DB_HOST: Keyword.fetch!(conn_args, :hostname),
183
        DB_PORT: "#{Keyword.fetch!(conn_args, :port)}",
14✔
184
        DB_SSL: "#{conn_args[:ssl]}",
14✔
185
        DB_SSL_VERIFY: "#{conn_args[:ssl_opts][:verify] == :verify_peer}"
14✔
186
      })
187

188
    user_secret =
14✔
189
      ~y"""
190
      apiVersion: v1
191
      kind: Secret
192
      metadata:
193
          namespace: #{K8s.Resource.FieldAccessors.namespace(axn.resource)}
14✔
194
          name: #{secret_name}
14✔
195
      """
196
      |> Map.put("stringData", data)
197

198
    username = user_env[:DB_USER]
14✔
199
    status_entry = %{"username" => username, "secret" => secret_name}
14✔
200

201
    axn =
14✔
202
      axn
203
      |> Bonny.Axn.register_descendant(user_secret)
204
      |> Bonny.Axn.update_status(fn status ->
205
        Map.update(status, "users", [status_entry], &Enum.uniq([status_entry | &1]))
14✔
206
      end)
207

208
    {:ok, axn}
209
  end
210

211
  @spec apply_user(
212
          username :: binary(),
213
          user_access :: Privileges.access(),
214
          Bonny.Axn.t(),
215
          Postgrex.conn(),
216
          conn_args :: Keyword.t(),
217
          db_name :: binary()
218
        ) :: {:ok, Bonny.Axn.t()} | {:error, binary()}
219
  defp apply_user(username, user_access, axn, conn, conn_args, db_name) do
220
    resource_name = K8s.Resource.FieldAccessors.name(axn.resource)
14✔
221
    resource_namespace = K8s.Resource.FieldAccessors.namespace(axn.resource)
14✔
222
    secret_name = Slugger.slugify_downcase("psql-#{resource_name}-#{username}", ?-)
14✔
223
    password = get_user_password(axn.conn, resource_namespace, secret_name)
14✔
224
    %{"session_authorization" => superuser} = Postgrex.parameters(conn)
14✔
225

226
    with {:ok, user_env} <- User.apply(username, conn, db_name, password),
14✔
227
         :ok <- Privileges.grant(user_env[:DB_USER], superuser, conn),
14✔
228
         :ok <- Privileges.grant(user_env[:DB_USER], user_access, db_name, conn) do
14✔
229
      apply_user_secret(axn, secret_name, user_env, conn_args)
14✔
230
    else
231
      {:error, error} ->
232
        {:error, axn, error}
×
233
    end
234
  end
235

236
  @spec drop_users(users :: list(map()), db_name :: binary(), Postgrex.conn()) ::
237
          :ok | {:error, binary()}
238
  defp drop_users(users, db_name, conn) do
239
    %{"session_authorization" => superuser} = Postgrex.parameters(conn)
8✔
240

241
    users
242
    |> List.wrap()
243
    |> Enum.uniq()
244
    |> Enum.find_value(:ok, fn
8✔
245
      %{"username" => username} ->
246
        with :ok <- Privileges.revoke(username, :all, db_name, conn),
14✔
247
             :ok <- User.drop(username, superuser, conn) do
14✔
248
          false
249
        end
250
    end)
251
  end
252

253
  @spec get_user_password(K8s.Conn.t(), namespace :: binary(), name :: binary()) :: binary()
254
  defp get_user_password(conn, namespace, name) do
255
    case K8s.Client.get("v1", "Secret", namespace: namespace, name: name)
14✔
256
         |> K8s.Client.put_conn(conn)
257
         |> K8s.Client.run() do
258
      {:ok, secret} -> secret["data"]["DB_PASS"] |> Base.decode64!()
×
259
      {:error, _} -> Password.random_string()
14✔
260
    end
261
  end
262

263
  @spec add_finalizer?(Bonny.Axn.t()) :: boolean()
264
  def add_finalizer?(%Bonny.Axn{resource: resource}) do
265
    conditions =
9✔
266
      resource
267
      |> get_in([Access.key("status", %{}), Access.key("conditions", [])])
268
      |> Map.new(&{&1["type"], &1})
35✔
269

270
    resource["metadata"]["annotations"]["kompost.chuge.li/deletion-policy"] != "abandon" and
9✔
271
      conditions["Connection"]["status"] == "True"
9✔
272
  end
273

274
  @spec instance_id(resource :: map()) :: Instance.id()
275
  defp instance_id(%{"spec" => %{"instanceRef" => %{}}} = resource) do
9✔
276
    {resource["metadata"]["namespace"], resource["spec"]["instanceRef"]["name"]}
277
  end
278

279
  defp instance_id(%{"spec" => %{"clusterInstanceRef" => %{}}} = resource) do
8✔
280
    {:cluster, resource["spec"]["clusterInstanceRef"]["name"]}
281
  end
282
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