Deployment and lifecycle management for Nix
0
fork

Configure Feed

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

cli: add seed submit

+267
+46
apps/sower_cli/lib/sower_cli.ex
··· 81 81 SowerCli.Seed.Upgrade.run(flags, options) 82 82 end 83 83 84 + defp run({[:seed, :submit], %{flags: flags, options: options}}) do 85 + SowerCli.Seed.Submit.run(flags, options) 86 + end 87 + 84 88 defp run({subcommands, _}) when is_list(subcommands) do 85 89 print_usage(subcommands) 86 90 end ··· 369 373 long: "--tag", 370 374 value_name: "KEY=VALUE", 371 375 help: "Filter by tag (can be repeated)", 376 + multiple: true 377 + ] 378 + ] 379 + ], 380 + submit: [ 381 + name: "submit", 382 + about: "Submit a seed to the server", 383 + flags: [ 384 + debug: [ 385 + short: "-d", 386 + long: "--debug", 387 + help: "Enable debug logging" 388 + ] 389 + ], 390 + options: [ 391 + type: [ 392 + short: "-t", 393 + long: "--type", 394 + value_name: "TYPE", 395 + help: "Seed type (nixos, home-manager, nix-darwin, service)", 396 + required: true, 397 + parser: &parse_seed_type/1 398 + ], 399 + name: [ 400 + short: "-n", 401 + long: "--name", 402 + value_name: "NAME", 403 + help: "Seed name", 404 + required: true 405 + ], 406 + artifact: [ 407 + short: "-a", 408 + long: "--artifact", 409 + value_name: "PATH", 410 + help: "Nix store path (e.g., /nix/store/...)", 411 + required: true 412 + ], 413 + tag: [ 414 + short: "-T", 415 + long: "--tag", 416 + value_name: "KEY=VALUE", 417 + help: "Add metadata tag (can be repeated)", 372 418 multiple: true 373 419 ] 374 420 ]
+51
apps/sower_cli/lib/sower_cli/seed/submit.ex
··· 1 + defmodule SowerCli.Seed.Submit do 2 + @moduledoc """ 3 + Submit a seed to the server. 4 + 5 + Registers a Nix store path as a seed with the given name, type, and optional tags. 6 + """ 7 + 8 + require Logger 9 + 10 + alias SowerCli.{Auth, Output} 11 + 12 + def run(flags, options) do 13 + Output.init(debug: flags.debug) 14 + Application.ensure_all_started([:req]) 15 + 16 + with :ok <- Auth.verify_connection(), 17 + {:ok, seed} <- submit_seed(options) do 18 + Output.success("Seed registered: #{seed.name} (#{seed.seed_type}) -> #{seed.artifact}") 19 + :ok 20 + else 21 + {:error, reason} -> 22 + {:error, reason} 23 + end 24 + end 25 + 26 + defp submit_seed(options) do 27 + tags = parse_tags(options.tag || []) 28 + 29 + seed = %SowerClient.Seed{ 30 + name: options.name, 31 + seed_type: options.type, 32 + artifact: options.artifact, 33 + tags: tags 34 + } 35 + 36 + Output.step("Submitting seed #{seed.name} (#{seed.seed_type})") 37 + 38 + case SowerClient.Seed.create(seed, []) do 39 + {:ok, %SowerClient.Seed{} = registered} -> 40 + {:ok, registered} 41 + 42 + {:error, reason} -> 43 + Output.error("Failed to submit seed: #{inspect(reason)}") 44 + {:error, {:submit_failed, reason}} 45 + end 46 + end 47 + 48 + defp parse_tags(tag_strings) do 49 + Enum.map(tag_strings, &SowerClient.SeedTag.from_string/1) 50 + end 51 + end
+170
apps/sower_cli/test/sower_cli/seed/submit_test.exs
··· 1 + defmodule SowerCli.Seed.SubmitTest do 2 + use ExUnit.Case, async: true 3 + 4 + import ExUnit.CaptureIO 5 + 6 + describe "command parsing" do 7 + test "parses required options" do 8 + config = SowerCli.config() 9 + 10 + {:ok, [:seed, :submit], parsed} = 11 + Optimus.parse(config, [ 12 + "seed", 13 + "submit", 14 + "-t", 15 + "nixos", 16 + "-n", 17 + "myhost", 18 + "-a", 19 + "/nix/store/abc123-nixos-system-myhost-25.05" 20 + ]) 21 + 22 + assert parsed.options.type == "nixos" 23 + assert parsed.options.name == "myhost" 24 + assert parsed.options.artifact == "/nix/store/abc123-nixos-system-myhost-25.05" 25 + end 26 + 27 + test "parses multiple tags" do 28 + config = SowerCli.config() 29 + 30 + {:ok, [:seed, :submit], parsed} = 31 + Optimus.parse(config, [ 32 + "seed", 33 + "submit", 34 + "-t", 35 + "nixos", 36 + "-n", 37 + "myhost", 38 + "-a", 39 + "/nix/store/abc123-nixos", 40 + "-T", 41 + "env=prod", 42 + "-T", 43 + "branch=main" 44 + ]) 45 + 46 + assert parsed.options.tag == ["env=prod", "branch=main"] 47 + end 48 + 49 + test "accepts all valid seed types" do 50 + config = SowerCli.config() 51 + 52 + for seed_type <- ["nixos", "home-manager", "nix-darwin", "service"] do 53 + {:ok, [:seed, :submit], parsed} = 54 + Optimus.parse(config, [ 55 + "seed", 56 + "submit", 57 + "-t", 58 + seed_type, 59 + "-n", 60 + "test", 61 + "-a", 62 + "/nix/store/abc123" 63 + ]) 64 + 65 + assert parsed.options.type == seed_type 66 + end 67 + end 68 + 69 + test "rejects invalid seed type" do 70 + config = SowerCli.config() 71 + 72 + result = 73 + Optimus.parse(config, [ 74 + "seed", 75 + "submit", 76 + "-t", 77 + "invalid", 78 + "-n", 79 + "test", 80 + "-a", 81 + "/nix/store/abc123" 82 + ]) 83 + 84 + assert {:error, [:seed, :submit], _} = result 85 + end 86 + 87 + test "requires --type option" do 88 + config = SowerCli.config() 89 + 90 + result = 91 + Optimus.parse(config, [ 92 + "seed", 93 + "submit", 94 + "-n", 95 + "myhost", 96 + "-a", 97 + "/nix/store/abc123" 98 + ]) 99 + 100 + assert {:error, [:seed, :submit], _} = result 101 + end 102 + 103 + test "requires --name option" do 104 + config = SowerCli.config() 105 + 106 + result = 107 + Optimus.parse(config, [ 108 + "seed", 109 + "submit", 110 + "-t", 111 + "nixos", 112 + "-a", 113 + "/nix/store/abc123" 114 + ]) 115 + 116 + assert {:error, [:seed, :submit], _} = result 117 + end 118 + 119 + test "requires --artifact option" do 120 + config = SowerCli.config() 121 + 122 + result = Optimus.parse(config, ["seed", "submit", "-t", "nixos", "-n", "myhost"]) 123 + 124 + assert {:error, [:seed, :submit], _} = result 125 + end 126 + 127 + test "parses debug flag" do 128 + config = SowerCli.config() 129 + 130 + {:ok, [:seed, :submit], parsed} = 131 + Optimus.parse(config, [ 132 + "seed", 133 + "submit", 134 + "-t", 135 + "nixos", 136 + "-n", 137 + "myhost", 138 + "-a", 139 + "/nix/store/abc123", 140 + "-d" 141 + ]) 142 + 143 + assert parsed.flags.debug == true 144 + end 145 + end 146 + 147 + describe "run/2" do 148 + test "returns error when server config is missing" do 149 + Application.put_env(:sower_cli, :config, %SowerClient.Config{ 150 + endpoint: nil, 151 + access_token: nil 152 + }) 153 + 154 + output = 155 + capture_io(fn -> 156 + result = 157 + SowerCli.Seed.Submit.run(%{debug: false}, %{ 158 + name: "test", 159 + type: "nixos", 160 + artifact: "/nix/store/abc123", 161 + tag: [] 162 + }) 163 + 164 + assert {:error, :missing_server_config} = result 165 + end) 166 + 167 + assert output =~ "endpoint is required" or output =~ "access_token is required" 168 + end 169 + end 170 + end