diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f929ae..1aedaed 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## Unreleased - Use new cypher names +- Added suppport for `kubernetes_node_basename` to supply a function callback which gets +the node info as arg supplied. ### 3.3.0 diff --git a/lib/strategy/kubernetes.ex b/lib/strategy/kubernetes.ex index 0965e5a..41edb05 100644 --- a/lib/strategy/kubernetes.ex +++ b/lib/strategy/kubernetes.ex @@ -19,6 +19,22 @@ defmodule Cluster.Strategy.Kubernetes do longname, `@`, `basename` would be the value configured in `kubernetes_node_basename`. + In the case when connecting different pods with different basenames + `kubernetes_node_basename` can also be applied with a function which returns + based on the supplied node info the node basename. If you have 2 apps running + such as oban-workers & phoenix api and specific as selector: + `kubernetes_selector: "app in (oban-workers, api)"` + but then want different basenames so you can differentiate between them + you could specifc it as following: + `kubernetes_node_basename: fn %{labels: %{"app" => app}} -> + case app do + "oban-workers" -> "oban" + "api" -> "api" + end + end` + + This allows you to connect different elixir deployements which have different basenames. + `domain` would be the value configured in `mode` and can be either of type `:ip` (the pod's ip, can be obtained by setting an env variable to status.podIP), `:hostname` or `:dns`, which is the pod's internal A Record. This A Record has the format @@ -226,6 +242,14 @@ defmodule Cluster.Strategy.Kubernetes do end cond do + not app_name_valid?(app_name) -> + warn( + topology, + "kubernetes strategy is selected, but :kubernetes_node_basename is not configured!" + ) + + [] + app_name != nil and selector != nil -> selector = URI.encode(selector) @@ -242,6 +266,12 @@ defmodule Cluster.Strategy.Kubernetes do {:ok, {{_version, 200, _status}, _headers, body}} -> parse_response(ip_lookup_mode, Jason.decode!(body)) |> Enum.map(fn node_info -> + app_name = + case app_name do + f when is_function(f, 1) -> f.(node_info) + s when is_binary(s) -> s + end + format_node( Keyword.get(config, :mode, :ip), node_info, @@ -265,14 +295,6 @@ defmodule Cluster.Strategy.Kubernetes do meta end - app_name == nil -> - warn( - topology, - "kubernetes strategy is selected, but :kubernetes_node_basename is not configured!" - ) - - [] - selector == nil -> warn( topology, @@ -291,13 +313,25 @@ defmodule Cluster.Strategy.Kubernetes do case resp do %{"items" => items} when is_list(items) -> Enum.reduce(items, [], fn - %{"subsets" => subsets}, acc when is_list(subsets) -> + %{"subsets" => subsets, "metadata" => %{"labels" => labels}}, acc + when is_list(subsets) -> addrs = Enum.flat_map(subsets, fn %{"addresses" => addresses} when is_list(addresses) -> - Enum.map(addresses, fn %{"ip" => ip, "targetRef" => %{"namespace" => namespace}} = - address -> - %{ip: ip, namespace: namespace, hostname: address["hostname"]} + Enum.map(addresses, fn %{ + "ip" => ip, + "targetRef" => %{ + "namespace" => namespace, + "name" => name + } + } = address -> + %{ + ip: ip, + namespace: namespace, + hostname: address["hostname"], + name: name, + labels: labels + } end) _ -> @@ -321,10 +355,10 @@ defmodule Cluster.Strategy.Kubernetes do Enum.map(items, fn %{ "status" => %{"podIP" => ip}, - "metadata" => %{"namespace" => ns}, + "metadata" => %{"namespace" => ns, "name" => name, "labels" => labels}, "spec" => pod_spec } -> - %{ip: ip, namespace: ns, hostname: pod_spec["hostname"]} + %{ip: ip, namespace: ns, hostname: pod_spec["hostname"], name: name, labels: labels} _ -> nil @@ -353,4 +387,8 @@ defmodule Cluster.Strategy.Kubernetes do ip = String.replace(ip, ".", "-") :"#{app_name}@#{ip}.#{namespace}.pod.#{cluster_name}.local" end + + defp app_name_valid?(s) when is_binary(s), do: true + defp app_name_valid?(f) when is_function(f, 1), do: true + defp app_name_valid?(_), do: false end diff --git a/test/kubernetes_test.exs b/test/kubernetes_test.exs index 2a86b70..59012e4 100644 --- a/test/kubernetes_test.exs +++ b/test/kubernetes_test.exs @@ -230,5 +230,34 @@ defmodule Cluster.Strategy.KubernetesTest do end) end end + + test "can create node names based of custom function" do + use_cassette "kubernetes", custom: true do + capture_log(fn -> + start_supervised!({Kubernetes, + [ + %Cluster.Strategy.State{ + topology: :name, + config: [ + kubernetes_node_basename: fn %{labels: labels} -> + app = Map.get(labels, "app") + app <> "-extended" + end, + kubernetes_selector: "app=test_selector", + # If you want to run the test freshly, you'll need to create a DNS Entry + kubernetes_master: "cluster.localhost.", + kubernetes_service_account_path: + Path.join([__DIR__, "fixtures", "kubernetes", "service_account"]) + ], + connect: {Nodes, :connect, [self()]}, + disconnect: {Nodes, :disconnect, [self()]}, + list_nodes: {Nodes, :list_nodes, [[]]} + } + ]}) + + assert_receive {:connect, :"development-extended@10.48.33.136"}, 5_000 + end) + end + end end end