Deployment and lifecycle management for Nix
0
fork

Configure Feed

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

cli: use owl livescreen for realtime updates

+145 -62
+83 -53
apps/sower_cli/lib/sower_cli/build.ex
··· 85 85 defp run_steps([:eval | rest], %__MODULE__{} = state) do 86 86 Output.step("Evaluating #{state.flake}") 87 87 88 - Application.ensure_all_started([:erlexec]) 88 + Application.ensure_all_started([:erlexec, :owl]) 89 89 90 90 opts = [ 91 91 workers: state.options.eval_jobs, ··· 98 98 task = Task.async(fn -> Nix.Eval.Jobs.run(state.flake, opts) end) 99 99 100 100 result = 101 - receive_progress(task, fn 102 - {:eval_started, attr} -> 103 - Output.item_start("Evaluating", attr || "(root)") 101 + receive_progress(task, %{}, fn msg, blocks -> 102 + case msg do 103 + {:eval_started, attr} -> 104 + name = attr || "(root)" 105 + block_id = {:eval, name} 106 + Output.live_item_start(block_id, "Evaluating", name) 107 + Map.put(blocks, name, block_id) 104 108 105 - {:eval_completed, attr, :ok} -> 106 - Output.item_done("Evaluated", attr || "(root)") 109 + {:eval_completed, attr, status} -> 110 + name = attr || "(root)" 111 + block_id = Map.get(blocks, name, {:eval, name}) 107 112 108 - {:eval_completed, attr, :branch} -> 109 - Output.item_done("Discovered", attr || "(root)") 113 + case status do 114 + :ok -> Output.live_item_done(block_id, "Evaluated", name) 115 + :branch -> Output.live_item_done(block_id, "Discovered", name) 116 + _ -> Output.live_item_error(block_id, "Eval failed", name) 117 + end 110 118 111 - {:eval_completed, attr, _error} -> 112 - Output.item_error("Eval failed", attr || "(root)") 119 + blocks 120 + end 113 121 end) 122 + 123 + Output.live_flush() 114 124 115 125 case result do 116 126 {:ok, %{results: results}} -> ··· 146 156 task = Task.async(fn -> Nix.Build.Jobs.run(state.evals, opts) end) 147 157 148 158 result = 149 - receive_progress(task, fn 150 - {:build_started, drv_path} -> 151 - Output.item_start("Building", drv_path || "(unknown)") 159 + receive_progress(task, %{}, fn msg, blocks -> 160 + case msg do 161 + {:build_started, drv_path} -> 162 + name = drv_path || "(unknown)" 163 + block_id = {:build, name} 164 + Output.live_item_start(block_id, "Building", name) 165 + Map.put(blocks, name, block_id) 152 166 153 - {:build_completed, drv_path, :ok} -> 154 - Output.item_done("Built", drv_path || "(unknown)") 167 + {:build_completed, drv_path, status} -> 168 + name = drv_path || "(unknown)" 169 + block_id = Map.get(blocks, name, {:build, name}) 155 170 156 - {:build_completed, drv_path, _error} -> 157 - Output.item_error("Build failed", drv_path || "(unknown)") 171 + case status do 172 + :ok -> Output.live_item_done(block_id, "Built", name) 173 + _ -> Output.live_item_error(block_id, "Build failed", name) 174 + end 175 + 176 + blocks 177 + end 158 178 end) 179 + 180 + Output.live_flush() 159 181 160 182 case result do 161 183 {:ok, job_result} -> ··· 229 251 client = SowerClient.ApiClient.new() 230 252 231 253 results = 232 - Enum.map(state.builds, fn 233 - %Nix.Build{ 234 - eval: %Nix.Eval{ 235 - output: %{ 236 - "meta" => %{ 237 - "sower" => %{ 238 - "seed" => seed_meta 239 - } 240 - } 241 - } 242 - } 243 - } = build -> 254 + state.builds 255 + |> Enum.with_index() 256 + |> Enum.map(fn 257 + {%Nix.Build{ 258 + eval: %Nix.Eval{ 259 + output: %{ 260 + "meta" => %{ 261 + "sower" => %{ 262 + "seed" => seed_meta 263 + } 264 + } 265 + } 266 + } 267 + } = build, idx} -> 244 268 seed_name = Map.get(seed_meta, "name", build.store_path) 245 - Output.item_start("Registering", seed_name) 269 + block_id = {:seed, idx} 270 + Output.live_item_start(block_id, "Registering", seed_name) 246 271 tags = load_tags(state) ++ Map.get(seed_meta, "tags", []) ++ SowerCli.Repo.get_tags() 247 272 248 - case seed_meta 249 - |> Map.put("tags", tags) 250 - |> Map.put("artifact", build.store_path) 251 - |> SowerClient.Seed.cast() do 252 - {:ok, seed} -> 253 - case SowerClient.Seed.create(client, seed) do 254 - {:ok, _} = result -> 255 - Output.item_done("Registered", seed_name) 256 - result 273 + result = 274 + case seed_meta 275 + |> Map.put("tags", tags) 276 + |> Map.put("artifact", build.store_path) 277 + |> SowerClient.Seed.cast() do 278 + {:ok, seed} -> 279 + case SowerClient.Seed.create(client, seed) do 280 + {:ok, _} = result -> 281 + Output.live_item_done(block_id, "Registered", seed_name) 282 + result 257 283 258 - {:error, reason} = error -> 259 - Output.item_error("Failed", seed_name) 260 - Output.error("Failed to register seed: #{inspect(reason)}") 261 - error 262 - end 284 + {:error, reason} = error -> 285 + Output.live_item_error(block_id, "Failed", seed_name) 286 + Output.error("Failed to register seed: #{inspect(reason)}") 287 + error 288 + end 263 289 264 - {:error, error} -> 265 - Output.item_error("Failed", seed_name) 266 - Output.error("Failed to cast seed: #{inspect(error)}") 267 - {:error, {:cast_failed, error}} 268 - end 290 + {:error, error} -> 291 + Output.live_item_error(block_id, "Failed", seed_name) 292 + Output.error("Failed to cast seed: #{inspect(error)}") 293 + {:error, {:cast_failed, error}} 294 + end 269 295 270 - %Nix.Build{eval: eval} -> 296 + result 297 + 298 + {%Nix.Build{eval: eval}, _idx} -> 271 299 Logger.debug(msg: "Eval is missing sower seed metadata", eval: eval) 272 300 :skip 273 301 end) 274 302 303 + Output.live_flush() 304 + 275 305 errors = 276 306 results 277 307 |> Enum.filter(fn ··· 295 325 |> Enum.map(&SowerClient.SeedTag.from_string/1) 296 326 end 297 327 298 - defp receive_progress(task, handler) do 328 + defp receive_progress(task, blocks, handler) do 299 329 receive do 300 330 {ref, result} when ref == task.ref -> 301 331 Process.demonitor(ref, [:flush]) ··· 305 335 {:error, {:task_crashed, reason}} 306 336 307 337 msg -> 308 - handler.(msg) 309 - receive_progress(task, handler) 338 + blocks = handler.(msg, blocks) 339 + receive_progress(task, blocks, handler) 310 340 end 311 341 end 312 342 end
+44 -9
apps/sower_cli/lib/sower_cli/output.ex
··· 4 4 """ 5 5 6 6 @doc """ 7 + Ensure Owl.LiveScreen is started. 8 + """ 9 + def ensure_live_screen do 10 + Application.ensure_all_started(:owl) 11 + end 12 + 13 + @doc """ 7 14 Print a step header. 8 15 """ 9 16 def step(name) do ··· 32 39 end 33 40 34 41 @doc """ 35 - Print an item starting message. 42 + Add a live-updating item block. Returns the block_id for later updates. 36 43 """ 37 - def item_start(action, name) do 38 - IO.puts(" #{IO.ANSI.yellow()}⋯#{IO.ANSI.reset()} #{action} #{name}") 44 + def live_item_start(block_id, action, name) do 45 + Owl.LiveScreen.add_block(block_id, 46 + state: {action, name, :pending}, 47 + render: &render_item/1 48 + ) 49 + 50 + Owl.LiveScreen.await_render() 51 + block_id 52 + end 53 + 54 + @doc """ 55 + Update a live item to show completion. 56 + """ 57 + def live_item_done(block_id, action, name) do 58 + Owl.LiveScreen.update(block_id, {action, name, :ok}) 59 + Owl.LiveScreen.await_render() 39 60 end 40 61 41 62 @doc """ 42 - Print an item completed message. 63 + Update a live item to show error. 43 64 """ 44 - def item_done(action, name) do 45 - IO.puts(" #{IO.ANSI.green()}✓#{IO.ANSI.reset()} #{action} #{name}") 65 + def live_item_error(block_id, action, name) do 66 + Owl.LiveScreen.update(block_id, {action, name, :error}) 67 + Owl.LiveScreen.await_render() 68 + end 69 + 70 + defp render_item({action, name, :pending}) do 71 + [" ", Owl.Data.tag("⋯", :yellow), " #{action} #{name}"] 72 + end 73 + 74 + defp render_item({action, name, :ok}) do 75 + [" ", Owl.Data.tag("✓", :green), " #{action} #{name}"] 76 + end 77 + 78 + defp render_item({action, name, :error}) do 79 + [" ", Owl.Data.tag("✗", :red), " #{action} #{name}"] 46 80 end 47 81 48 82 @doc """ 49 - Print an item error message. 83 + Flush all live blocks and render final state. 50 84 """ 51 - def item_error(action, name) do 52 - IO.puts(" #{IO.ANSI.red()}✗#{IO.ANSI.reset()} #{action} #{name}") 85 + def live_flush do 86 + Owl.LiveScreen.await_render() 87 + Owl.LiveScreen.flush() 53 88 end 54 89 55 90 @doc """
+1
apps/sower_cli/mix.exs
··· 34 34 [ 35 35 {:nix, in_umbrella: true}, 36 36 {:optimus, "~> 0.5"}, 37 + {:owl, "~> 0.13"}, 37 38 {:sower_client, in_umbrella: true}, 38 39 {:typedstruct, "~> 0.5.4"} 39 40 ]
+17
nix/packages/deps.nix
··· 818 818 in 819 819 drv; 820 820 821 + owl = 822 + let 823 + version = "0.13.0"; 824 + drv = buildMix { 825 + inherit version; 826 + name = "owl"; 827 + appConfigPath = ../../config; 828 + 829 + src = fetchHex { 830 + inherit version; 831 + pkg = "owl"; 832 + sha256 = "59bf9d11ce37a4db98f57cb68fbfd61593bf419ec4ed302852b6683d3d2f7475"; 833 + }; 834 + }; 835 + in 836 + drv; 837 + 821 838 permit = 822 839 let 823 840 version = "0.3.0";