Deployment and lifecycle management for Nix
0
fork

Configure Feed

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

feat: report and display garden version

Garden now sends a structured GardenReport (version) over a new
garden:report channel message on private-channel join. Server persists
it on the gardens row and the garden show page renders a Details
section with the reported version.

sow-172

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

+78 -1
+13
apps/garden/lib/garden/socket.ex
··· 7 7 alias Garden.Socket.Lifecycle 8 8 alias Garden.Storage 9 9 alias SowerClient.Orchestration.DeploymentStatus 10 + alias SowerClient.Orchestration.GardenReport 10 11 alias SowerClient.Orchestration.SeedDeploymentStatus 11 12 12 13 def deploy(%SowerClient.Orchestration.Subscription{} = sub, opts \\ []) do ··· 42 43 43 44 {:ok, _ref} = push(socket, private_channel(socket), "garden:seeds:report", report) 44 45 end 46 + 47 + {:noreply, socket} 48 + end 49 + 50 + @impl Slipstream 51 + def handle_cast(:report_garden, socket) do 52 + report = GardenReport.cast!(%{version: to_string(Application.spec(:garden, :vsn))}) 53 + 54 + Logger.debug(msg: "Reporting garden facts", version: report.version) 55 + 56 + {:ok, _ref} = push_message(socket, report) 45 57 46 58 {:noreply, socket} 47 59 end ··· 378 390 379 391 cast(:sync_subscriptions) 380 392 cast(:report_seeds) 393 + cast(:report_garden) 381 394 382 395 {:ok, assign(socket, :conn_sid, conn_sid)} 383 396 end
+1
apps/sower/lib/sower/orchestration.ex
··· 21 21 defdelegate list_gardens_with_latest_deployment(), to: Garden 22 22 defdelegate list_gardens_flop(params \\ %{}), to: Garden 23 23 defdelegate update_garden(garden, attrs), to: Garden 24 + defdelegate update_garden_report(garden, report), to: Garden 24 25 25 26 # Subscription delegates 26 27 defdelegate change_subscription(subscription, attrs \\ %{}), to: Subscription
+9 -1
apps/sower/lib/sower/orchestration/garden.ex
··· 27 27 field :name, :string 28 28 field :org_id, Ecto.UUID 29 29 field :oauth_client_id, :string 30 + field :version, :string 30 31 31 32 has_many :subscriptions, Sower.Orchestration.Subscription 32 33 has_many :deployments, Sower.Orchestration.Deployment ··· 40 41 @doc false 41 42 def changeset(garden, attrs) do 42 43 garden 43 - |> cast(attrs, [:name, :org_id, :oauth_client_id]) 44 + |> cast(attrs, [:name, :org_id, :oauth_client_id, :version]) 44 45 |> validate_required([:name]) 45 46 end 46 47 ··· 118 119 garden 119 120 |> changeset(attrs) 120 121 |> Repo.update() 122 + end 123 + 124 + def update_garden_report( 125 + %__MODULE__{} = garden, 126 + %SowerClient.Orchestration.GardenReport{} = report 127 + ) do 128 + update_garden(garden, %{version: report.version}) 121 129 end 122 130 123 131 defp delete_existing_client(%__MODULE__{oauth_client_id: nil}), do: :ok
+4
apps/sower/lib/sower_web/garden_channel.ex
··· 146 146 Orchestration.update_garden_seed_generations(report, socket.assigns.garden) 147 147 end) 148 148 149 + handle_schema(SowerClient.Orchestration.GardenReport, fn report, socket -> 150 + Orchestration.update_garden_report(socket.assigns.garden, report) 151 + end) 152 + 149 153 @impl Phoenix.Channel 150 154 def handle_info(:track_presence, %Phoenix.Socket{assigns: %{garden: garden}} = socket) do 151 155 Logger.debug(msg: "Tracking garden presence", garden_sid: garden.sid)
+10
apps/sower/lib/sower_web/live/garden_live/show.html.heex
··· 16 16 17 17 <div class="mt-8 space-y-10"> 18 18 <section> 19 + <h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-200 mb-4">Details</h2> 20 + <dl class="text-sm text-zinc-700 dark:text-zinc-300"> 21 + <div class="flex gap-2"> 22 + <dt class="font-medium">Version:</dt> 23 + <dd>{@garden.version || "unknown"}</dd> 24 + </div> 25 + </dl> 26 + </section> 27 + 28 + <section> 19 29 <div class="flex items-center justify-between mb-4"> 20 30 <h2 class="text-sm font-semibold text-zinc-900 dark:text-zinc-200">Seed Generations</h2> 21 31 <div class="flex items-center space-x-2">
+9
apps/sower/priv/repo/migrations/20260418203445_add_version_to_gardens.exs
··· 1 + defmodule Sower.Repo.Migrations.AddVersionToGardens do 2 + use Ecto.Migration 3 + 4 + def change do 5 + alter table(:gardens) do 6 + add :version, :string, null: true 7 + end 8 + end 9 + end
+11
apps/sower/test/sower_web/channels/garden_channel_handle_in_test.exs
··· 388 388 assert_reply ref, :ok, :ok, 1_000 389 389 end 390 390 end 391 + 392 + describe "garden:report" do 393 + test "persists reported version on the garden row" do 394 + %{socket: socket, garden: garden} = connect_and_join_garden() 395 + 396 + ref = push(socket, "garden:report", %{"version" => "1.2.3"}) 397 + 398 + assert_reply ref, :ok, _reply, 1_000 399 + assert Sower.Orchestration.get_garden!(garden.id).version == "1.2.3" 400 + end 401 + end 391 402 end
+1
apps/sower_client/lib/sower_client.ex
··· 29 29 SowerClient.Orchestration.GardenSeedGeneration, 30 30 SowerClient.Orchestration.GardenSeedProfile, 31 31 SowerClient.Orchestration.GardenSeedsReport, 32 + SowerClient.Orchestration.GardenReport, 32 33 SowerClient.Orchestration.Deployment, 33 34 SowerClient.Orchestration.DeploymentResult, 34 35 SowerClient.Orchestration.DeploymentRequest,
+20
apps/sower_client/lib/sower_client/orchestration/garden_report.ex
··· 1 + defmodule SowerClient.Orchestration.GardenReport do 2 + @moduledoc """ 3 + Garden-level facts a garden reports to the server. 4 + Sent when the garden joins its private channel. 5 + """ 6 + use SowerClient.Schema 7 + use SowerClient.ChannelMessage, event: "garden:report" 8 + 9 + OpenApiSpex.schema(%{ 10 + title: "GardenReport", 11 + type: :object, 12 + properties: %{ 13 + version: %Schema{ 14 + type: :string, 15 + description: "Software version the garden is running" 16 + } 17 + }, 18 + required: [:version] 19 + }) 20 + end