Deployment and lifecycle management for Nix
0
fork

Configure Feed

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

server/agent: record deployment result

+113 -65
+1 -1
apps/sower/lib/sower/orchestration.ex
··· 451 451 {:error, :deployment_not_found} 452 452 453 453 deploy -> 454 - update_deployment(deploy, %{deployed_at: result.deployed_at}) 454 + update_deployment(deploy, %{deployed_at: result.deployed_at, result: result.result}) 455 455 end 456 456 end 457 457 end
+2 -1
apps/sower/lib/sower/orchestration/deployment.ex
··· 17 17 many_to_many :seeds, Sower.Seed, join_through: Orchestration.SeedDeployment 18 18 19 19 field :deployed_at, :utc_datetime 20 + field :result, Ecto.Enum, values: [:success, :failure, :partial] 20 21 21 22 timestamps() 22 23 end ··· 24 25 @doc false 25 26 def changeset(deployment, attrs) do 26 27 deployment 27 - |> cast(attrs, [:deployed_at]) 28 + |> cast(attrs, [:deployed_at, :result]) 28 29 |> put_assoc(:seeds, Map.get(attrs, :seeds, deployment.seeds)) 29 30 |> put_assoc(:subscriptions, Map.get(attrs, :subscriptions, deployment.subscriptions)) 30 31 |> validate_required([])
+1
apps/sower/priv/repo/migrations/20250531133248_create_deployments.exs
··· 7 7 add :org_id, references(:organizations, column: :org_id, type: :uuid), null: false 8 8 9 9 add :deployed_at, :utc_datetime 10 + add :result, :string 10 11 11 12 timestamps() 12 13 end
+2 -1
apps/sower_agent/lib/sower_agent/application.ex
··· 10 10 children = [ 11 11 # Starts a worker by calling: SowerAgent.Worker.start_link(arg) 12 12 {SowerAgent.SocketClient, []}, 13 - {SowerAgent.Storage, []} 13 + {SowerAgent.Storage, []}, 14 + {Task.Supervisor, name: SowerAgent.TaskSupervisor} 14 15 ] 15 16 16 17 # See https://hexdocs.pm/elixir/Supervisor.html
+2
apps/sower_agent/lib/sower_agent/seed.ex
··· 18 18 Logger.error(msg: "Failed to activate", output: output, return_code: code) 19 19 {:error, code} 20 20 end 21 + else 22 + {:ok, ["noop"]} 21 23 end 22 24 end 23 25
+22 -58
apps/sower_agent/lib/sower_agent/socket_client.ex
··· 220 220 221 221 case SowerClient.Schemas.Orchestration.Deployment.cast(response) do 222 222 {:ok, deployment} -> 223 - dbg(deployment) 224 - 225 223 Logger.debug( 226 224 msg: "Received deployment", 227 225 request_id: deployment.request_id, 228 226 deployment_sid: deployment.sid 229 227 ) 230 228 231 - result = 232 - deployment.seeds 233 - |> Enum.map(fn seed -> 234 - Logger.debug( 235 - msg: "Realizing seed", 236 - name: seed.name, 237 - seed_sid: seed.sid, 238 - seed_type: seed.seed_type, 239 - artifact: seed.artifact 240 - ) 241 - 242 - case System.cmd("nix-store", ["--realize", seed.artifact], 243 - stderr_to_stdout: true, 244 - into: [], 245 - lines: 1024 246 - ) do 247 - {_output, 0} -> 248 - Logger.info( 249 - msg: "Successfully realized seed", 250 - name: seed.name, 251 - seed_sid: seed.sid, 252 - seed_type: seed.seed_type, 253 - artifact: seed.artifact 254 - ) 255 - 256 - {output, exit_code} -> 257 - output = 258 - Enum.filter(output, fn line -> 259 - line not in [ 260 - "warning: you did not specify '--add-root'; the result might be removed by the garbage collector" 261 - ] 262 - end) 229 + deploy_result = SowerAgent.TaskRunner.upgrade(deployment.seeds) 263 230 264 - Logger.error( 265 - msg: "Failed to realize seed", 266 - name: seed.name, 267 - seed_sid: seed.sid, 268 - seed_type: seed.seed_type, 269 - artifact: seed.artifact, 270 - exit_code: exit_code, 271 - output: output 272 - ) 231 + result = 232 + Enum.all?(deploy_result, fn r -> 233 + case r do 234 + {:ok, {:ok, _}} -> true 235 + _ -> false 273 236 end 274 - 275 - seed 276 237 end) 277 - |> Enum.map(fn seed -> 278 - Logger.info( 279 - msg: "Activating seed", 280 - name: seed.name, 281 - seed_sid: seed.sid, 282 - seed_type: seed.seed_type, 283 - artifact: seed.artifact 284 - ) 285 - 286 - SowerAgent.Seed.activate(seed) 287 - end) 238 + |> case do 239 + true -> 240 + :success 288 241 289 - success = Enum.all?(result, fn r -> {:ok, _} = r end) 242 + false -> 243 + Enum.any?(deploy_result, fn r -> 244 + case r do 245 + {:ok, {:ok, _}} -> true 246 + _ -> false 247 + end 248 + end) 249 + |> case do 250 + true -> :partial 251 + false -> :failure 252 + end 253 + end 290 254 291 255 {:ok, result} = 292 256 SowerClient.Schemas.Orchestration.DeploymentResult.cast(%{ 293 257 request_id: deployment.request_id, 294 258 deployment_sid: deployment.sid, 295 - success: success, 259 + result: result, 296 260 deployed_at: DateTime.utc_now() |> DateTime.to_iso8601() 297 261 }) 298 262
+78
apps/sower_agent/lib/sower_agent/task_runner.ex
··· 1 + defmodule SowerAgent.TaskRunner do 2 + require Logger 3 + 4 + def upgrade(seeds) do 5 + seeds 6 + |> async_stream(fn seed -> 7 + Logger.debug( 8 + msg: "Realizing seed", 9 + name: seed.name, 10 + seed_sid: seed.sid, 11 + seed_type: seed.seed_type, 12 + artifact: seed.artifact 13 + ) 14 + 15 + case System.cmd("nix-store", ["--realize", seed.artifact], 16 + stderr_to_stdout: true, 17 + into: [], 18 + lines: 1024 19 + ) do 20 + {_output, 0} -> 21 + Logger.info( 22 + msg: "Successfully realized seed", 23 + name: seed.name, 24 + seed_sid: seed.sid, 25 + seed_type: seed.seed_type, 26 + artifact: seed.artifact 27 + ) 28 + 29 + {:ok, seed} 30 + 31 + {output, exit_code} -> 32 + output = 33 + Enum.filter(output, fn line -> 34 + line not in [ 35 + "warning: you did not specify '--add-root'; the result might be removed by the garbage collector" 36 + ] 37 + end) 38 + 39 + Logger.error( 40 + msg: "Failed to realize seed", 41 + name: seed.name, 42 + seed_sid: seed.sid, 43 + seed_type: seed.seed_type, 44 + artifact: seed.artifact, 45 + exit_code: exit_code, 46 + output: output 47 + ) 48 + 49 + {:error, :failed_to_realize, seed} 50 + end 51 + end) 52 + |> async_stream(fn 53 + {:ok, {:ok, seed}} -> 54 + Logger.info( 55 + msg: "Activating seed", 56 + name: seed.name, 57 + seed_sid: seed.sid, 58 + seed_type: seed.seed_type, 59 + artifact: seed.artifact 60 + ) 61 + 62 + SowerAgent.Seed.activate(seed) 63 + 64 + {:ok, {:error, _, _} = error} -> 65 + error 66 + 67 + {:exit, error} -> 68 + error 69 + end) 70 + |> Enum.to_list() 71 + end 72 + 73 + def async_stream(enumerable, func) do 74 + Task.Supervisor.async_stream_nolink(SowerAgent.TaskSupervisor, enumerable, func, 75 + max_concurrency: 3 76 + ) 77 + end 78 + end
+5 -4
apps/sower_client/lib/schemas/orchestration/deployment_result.ex
··· 13 13 type: :string, 14 14 description: "deployment sid which is being reported on" 15 15 }, 16 - success: %Schema{ 17 - type: :boolean, 18 - description: "result of the deployment" 16 + result: %Schema{ 17 + type: :string, 18 + description: "result of the deployment", 19 + enum: [:success, :failure, :partial] 19 20 }, 20 21 output: %Schema{ 21 22 type: :array, ··· 32 33 default: DateTime.from_unix!(0) |> DateTime.to_iso8601() 33 34 } 34 35 }, 35 - required: [:request_id, :deployment_sid, :success] 36 + required: [:request_id, :deployment_sid, :result] 36 37 }) 37 38 end