Deployment and lifecycle management for Nix
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor: extract merge_subscriptions and poll_on_connect into Garden.Socket.State

SOW-74

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+96 -18
+2 -18
apps/garden/lib/garden/socket.ex
··· 63 63 subscriptions = 64 64 case await_reply(ref) do 65 65 {:ok, %{"subscriptions" => registered}} -> 66 - # Build a map of (seed_name, seed_type) -> sid for quick lookup 67 - sid_map = 68 - registered 69 - |> Enum.map(&SowerClient.Orchestration.Subscription.cast!/1) 70 - |> Map.new(&{{&1.seed_name, &1.seed_type}, &1.sid}) 71 - 72 - # Merge server-assigned sids back into original subscriptions 73 - # to preserve garden-only gardens like schedule and poll_on_connect 74 - Enum.map(config_subscriptions, fn sub -> 75 - case Map.get(sid_map, {sub.seed_name, sub.seed_type}) do 76 - nil -> nil 77 - sid -> %{sub | sid: sid} 78 - end 79 - end) 80 - |> Enum.reject(&is_nil/1) 66 + State.merge_subscriptions(config_subscriptions, registered) 81 67 82 68 {:error, error} -> 83 69 Logger.error(msg: "Failed to sync subscriptions", error: error) ··· 85 71 end 86 72 87 73 Scheduler.refresh_subscriptions(subscriptions) 88 - 89 74 Garden.Storage.put(:subscriptions, subscriptions) 90 75 91 - subscriptions 92 - |> Enum.filter(& &1.poll_on_connect) 76 + State.poll_on_connect_subscriptions(subscriptions) 93 77 |> Enum.each(fn sub -> 94 78 Task.Supervisor.start_child(Garden.TaskSupervisor, fn -> 95 79 deploy(sub)
+21
apps/garden/lib/garden/socket/state.ex
··· 8 8 """ 9 9 10 10 alias SowerClient.Orchestration.DeploymentRequest 11 + alias SowerClient.Orchestration.Subscription 11 12 12 13 def build_seed_report( 13 14 subscriptions, ··· 33 34 end 34 35 35 36 DeploymentRequest.new(payload) 37 + end 38 + 39 + def merge_subscriptions(config_subscriptions, registered) do 40 + sid_map = 41 + registered 42 + |> Enum.map(&Subscription.cast!/1) 43 + |> Map.new(&{{&1.seed_name, &1.seed_type}, &1.sid}) 44 + 45 + config_subscriptions 46 + |> Enum.map(fn sub -> 47 + case Map.get(sid_map, {sub.seed_name, sub.seed_type}) do 48 + nil -> nil 49 + sid -> %{sub | sid: sid} 50 + end 51 + end) 52 + |> Enum.reject(&is_nil/1) 53 + end 54 + 55 + def poll_on_connect_subscriptions(subscriptions) do 56 + Enum.filter(subscriptions, & &1.poll_on_connect) 36 57 end 37 58 end
+73
apps/garden/test/garden/socket/state_test.exs
··· 4 4 alias Garden.Socket.State 5 5 alias SowerClient.Orchestration.DeploymentRequest 6 6 alias SowerClient.Orchestration.GardenSeedsReport 7 + alias SowerClient.Orchestration.Subscription 7 8 8 9 describe "build_deployment_request/2" do 9 10 test "builds request with subscription sid" do ··· 50 51 report = GardenSeedsReport.cast!(%{profiles: []}) 51 52 52 53 assert {:report, ^report} = State.build_seed_report([], fn _subs -> report end) 54 + end 55 + end 56 + 57 + describe "merge_subscriptions/2" do 58 + test "merges server-assigned sids into config subscriptions" do 59 + config_subs = [ 60 + Subscription.cast!(%{seed_name: "host", seed_type: "nixos", poll_on_connect: true}), 61 + Subscription.cast!(%{seed_name: "user", seed_type: "home-manager"}) 62 + ] 63 + 64 + registered = [ 65 + %{"seed_name" => "host", "seed_type" => "nixos", "sid" => "sub_abc"}, 66 + %{"seed_name" => "user", "seed_type" => "home-manager", "sid" => "sub_def"} 67 + ] 68 + 69 + result = State.merge_subscriptions(config_subs, registered) 70 + 71 + assert length(result) == 2 72 + assert Enum.find(result, &(&1.seed_name == "host")).sid == "sub_abc" 73 + assert Enum.find(result, &(&1.seed_name == "host")).poll_on_connect == true 74 + assert Enum.find(result, &(&1.seed_name == "user")).sid == "sub_def" 75 + end 76 + 77 + test "drops config subscriptions not registered on server" do 78 + config_subs = [ 79 + Subscription.cast!(%{seed_name: "host", seed_type: "nixos"}), 80 + Subscription.cast!(%{seed_name: "orphan", seed_type: "nixos"}) 81 + ] 82 + 83 + registered = [ 84 + %{"seed_name" => "host", "seed_type" => "nixos", "sid" => "sub_abc"} 85 + ] 86 + 87 + result = State.merge_subscriptions(config_subs, registered) 88 + 89 + assert length(result) == 1 90 + assert hd(result).seed_name == "host" 91 + end 92 + 93 + test "returns empty list for empty registered" do 94 + config_subs = [ 95 + Subscription.cast!(%{seed_name: "host", seed_type: "nixos"}) 96 + ] 97 + 98 + assert State.merge_subscriptions(config_subs, []) == [] 99 + end 100 + end 101 + 102 + describe "poll_on_connect_subscriptions/1" do 103 + test "filters to subscriptions with poll_on_connect true" do 104 + subs = [ 105 + Subscription.cast!(%{ 106 + seed_name: "host", 107 + seed_type: "nixos", 108 + poll_on_connect: true, 109 + sid: "sub_1" 110 + }), 111 + Subscription.cast!(%{seed_name: "user", seed_type: "home-manager", sid: "sub_2"}) 112 + ] 113 + 114 + result = State.poll_on_connect_subscriptions(subs) 115 + 116 + assert length(result) == 1 117 + assert hd(result).seed_name == "host" 118 + end 119 + 120 + test "returns empty list when none have poll_on_connect" do 121 + subs = [ 122 + Subscription.cast!(%{seed_name: "host", seed_type: "nixos", sid: "sub_1"}) 123 + ] 124 + 125 + assert State.poll_on_connect_subscriptions(subs) == [] 53 126 end 54 127 end 55 128 end