ALPHA: wire is a tool to deploy nixos systems wire.althaea.zone/

parse ApplyTarget's from stdin (#295)

authored by

marshmallow and committed by
GitHub
31127479 a48397cf

+85 -14
+1
CHANGELOG.md
··· 10 10 ### Added 11 11 12 12 - `--ssh-accept-host` was added. 13 + - `--on -` will now read additional apply targets from stdin. 13 14 14 15 ### Fixed 15 16
+7 -7
doc/guide/wire.md
··· 38 38 The following is the goal for a stable release and not fully implemented. 39 39 ::: 40 40 41 - | Features | Wire | Colmena | 42 - | --------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------- | 43 - | Secret Management | :white_check_mark: | :white_check_mark: | 44 - | Parallel Evaluation | :white_check_mark: | [Experimental](https://colmena.cli.rs/unstable/features/parallelism.html#parallel-evaluation-experimental) | 45 - | Node Tagging | :white_check_mark: | :white_check_mark: | 46 - | `jq` pipeline support | :white_check_mark: | :x:[^2] | 47 - | Magic Rollback | :white_check_mark: (Planned) | :x: | 41 + | Features | Wire | Colmena | 42 + | ------------------- | ---------------------------- | ---------------------------------------------------------------------------------------------------------- | 43 + | Secret Management | :white_check_mark: | :white_check_mark: | 44 + | Parallel Evaluation | :white_check_mark: | [Experimental](https://colmena.cli.rs/unstable/features/parallelism.html#parallel-evaluation-experimental) | 45 + | Node Tagging | :white_check_mark: | :white_check_mark: | 46 + | Pipeline Support | :white_check_mark: | :x:[^2] | 47 + | Magic Rollback | :white_check_mark: (Planned) | :x: | 48 48 49 49 [^2]: You need to write custom nix code to use Colmena hive metadata inside environments like CI pipelines, bash scripting, etc., which requires a knowledge of its internals.
+1
tests/nix/default.nix
··· 29 29 ./suite/test_remote_deploy 30 30 ./suite/test_local_deploy 31 31 ./suite/test_keys 32 + ./suite/test_stdin 32 33 ]; 33 34 options.wire.testing = mkOption { 34 35 type = attrsOf (
+15
tests/nix/suite/test_stdin/default.nix
··· 1 + # SPDX-License-Identifier: AGPL-3.0-or-later 2 + # Copyright 2024-2025 wire Contributors 3 + 4 + { 5 + wire.testing.test_stdin = { 6 + nodes.deployer = { 7 + _wire.deployer = true; 8 + _wire.receiver = true; 9 + }; 10 + testScript = '' 11 + deployer.succeed(f"echo @tag | wire apply --on deployer --no-progress --path {TEST_DIR}/hive.nix --no-keys -vvv >&2") 12 + deployer.succeed("test -f /etc/a") 13 + ''; 14 + }; 15 + }
+13
tests/nix/suite/test_stdin/hive.nix
··· 1 + # SPDX-License-Identifier: AGPL-3.0-or-later 2 + # Copyright 2024-2025 wire Contributors 3 + 4 + let 5 + inherit (import ../utils.nix { testName = "test_keys-@IDENT@"; }) makeHive mkHiveNode; 6 + in 7 + makeHive { 8 + meta.nixpkgs = import <nixpkgs> { localSystem = "x86_64-linux"; }; 9 + deployer = mkHiveNode { hostname = "deployer"; } { 10 + deployment.tags = [ "tag" ]; 11 + environment.etc."a".text = "b"; 12 + }; 13 + }
+37 -5
wire/cli/src/apply.rs
··· 6 6 use lib::hive::Hive; 7 7 use lib::hive::node::{Context, GoalExecutor, Name, StepState}; 8 8 use lib::{SubCommandModifiers, errors::HiveLibError}; 9 - use miette::{Diagnostic, Result}; 9 + use miette::{Diagnostic, IntoDiagnostic, Result}; 10 10 use std::collections::HashSet; 11 + use std::io::Read; 11 12 use std::path::PathBuf; 12 13 use std::sync::{Arc, Mutex}; 13 14 use thiserror::Error; ··· 28 29 #[error("{} node(s) failed to apply.", .0.len())] 29 30 struct NodeErrors(#[related] Vec<NodeError>); 30 31 32 + // returns Names and Tags 33 + fn read_apply_targets_from_stdin() -> Result<(Vec<String>, Vec<Name>)> { 34 + let mut buf = String::new(); 35 + let mut stdin = std::io::stdin().lock(); 36 + stdin.read_to_string(&mut buf).into_diagnostic()?; 37 + 38 + Ok(buf 39 + .split_whitespace() 40 + .map(|x| ApplyTarget::from(x.to_string())) 41 + .fold((Vec::new(), Vec::new()), |(mut tags, mut names), target| { 42 + match target { 43 + ApplyTarget::Node(name) => names.push(name), 44 + ApplyTarget::Tag(tag) => tags.push(tag), 45 + ApplyTarget::Stdin => {} 46 + } 47 + (tags, names) 48 + })) 49 + } 50 + 31 51 #[instrument(skip_all, fields(goal = %args.goal, on = %args.on.iter().join(", ")))] 32 52 pub async fn apply( 33 53 hive: &mut Hive, 34 54 args: ApplyArgs, 35 55 path: PathBuf, 36 - modifiers: SubCommandModifiers, 56 + mut modifiers: SubCommandModifiers, 37 57 clobber_lock: Arc<Mutex<()>>, 38 58 ) -> Result<()> { 39 59 let header_span = Span::current(); ··· 47 67 (HashSet::new(), HashSet::new()), 48 68 |(mut tags, mut names), target| { 49 69 match target { 50 - ApplyTarget::Tag(tag) => tags.insert(tag.clone()), 51 - ApplyTarget::Node(name) => names.insert(name.clone()), 52 - }; 70 + ApplyTarget::Tag(tag) => { 71 + tags.insert(tag.clone()); 72 + } 73 + ApplyTarget::Node(name) => { 74 + names.insert(name.clone()); 75 + } 76 + ApplyTarget::Stdin => { 77 + // implies non_interactive 78 + modifiers.non_interactive = true; 79 + 80 + let (found_tags, found_names) = read_apply_targets_from_stdin().unwrap(); 81 + names.extend(found_names); 82 + tags.extend(found_tags); 83 + } 84 + } 53 85 (tags, names) 54 86 }, 55 87 );
+11 -2
wire/cli/src/cli.rs
··· 61 61 pub enum ApplyTarget { 62 62 Node(Name), 63 63 Tag(String), 64 + Stdin, 64 65 } 65 66 66 67 impl From<String> for ApplyTarget { 67 68 fn from(value: String) -> Self { 69 + if value == "-" { 70 + return ApplyTarget::Stdin; 71 + } 72 + 68 73 if let Some(stripped) = value.strip_prefix("@") { 69 74 ApplyTarget::Tag(stripped.to_string()) 70 75 } else { ··· 78 83 match self { 79 84 ApplyTarget::Node(name) => name.fmt(f), 80 85 ApplyTarget::Tag(tag) => write!(f, "@{tag}"), 86 + ApplyTarget::Stdin => write!(f, "#stdin"), 81 87 } 82 88 } 83 89 } ··· 91 97 #[arg(value_enum, default_value_t)] 92 98 pub goal: Goal, 93 99 94 - /// List of literal node names or `@` prefixed tags. 95 - #[arg(short, long, value_name = "NODE | @TAG", num_args = 1..)] 100 + /// List of literal node names, a literal `-`, or `@` prefixed tags. 101 + /// 102 + /// `-` will read additional values from stdin, seperated by whitespace. 103 + /// Any `-` implies `--non-interactive`. 104 + #[arg(short, long, value_name = "NODE | @TAG | `-`", num_args = 1..)] 96 105 pub on: Vec<ApplyTarget>, 97 106 98 107 #[arg(short, long, default_value_t = 10, value_parser=more_than_zero)]