From 7c00fd2761e6efffe763ece5d08d9a6d3fb95092 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 5 Oct 2020 19:46:28 -0700 Subject: Add interface with json schema, fix flake-less issues, put setActivate and jsonSchema check in flake lib --- interface/README.md | 32 ++++++++++++++++ interface/deploy.json | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 interface/README.md create mode 100644 interface/deploy.json (limited to 'interface') diff --git a/interface/README.md b/interface/README.md new file mode 100644 index 0000000..c6b52bd --- /dev/null +++ b/interface/README.md @@ -0,0 +1,32 @@ +A flake must have a `deploy` output with the following structure: + +``` +deploy +├── +└── nodes + ├── + │   ├── + │   ├── hostname + │   └── profiles + │   ├── + │   │   ├── + │   │   ├── bootstrap + │   │   └── path + │   └── ... + └── ... + +``` + +Where `` are all optional and can be one or multiple of: + +- `sshUser` -- user to connect as +- `user` -- user to install and activate profiles with +- `sshOpts` -- options passed to `nix copy` and `ssh` +- `fastConnection` -- whether the connection from this host to the target one is fast (if it is, don't substitute on target and copy the entire closure) [default: `false`] +- `autoRollback` -- whether to roll back when the deployment fails [default: `false`] + +A formal definition for the structure can be found in [the JSON schema](./deploy.json) + +For every profile of every node, arguments are merged with `` taking precedence over `` and `` taking precedence over top-level. + +Values can be overridden for all the profiles deployed by setting environment variables with the same names as the profile, for example `sshUser=foobar nix run github:serokell/deploy .` will connect to all nodes as `foobar@.hostname`. diff --git a/interface/deploy.json b/interface/deploy.json new file mode 100644 index 0000000..aaa6534 --- /dev/null +++ b/interface/deploy.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft/2019-09/schema#", + "title": "Deploy", + "description": "Matches a correct deploy attribute of a flake", + "definitions": { + "generic_settings": { + "type": "object", + "properties": { + "sshUser": { + "type": "string" + }, + "user": { + "type": "string" + }, + "sshOpts": { + "type": "array", + "items": { + "type": "string" + } + }, + "fastConnection": { + "type": "boolean" + }, + "autoRollback": { + "type": "boolean" + } + } + }, + "node_settings": { + "type": "object", + "properties": { + "hostname": { + "type": "string" + } + }, + "required": [ + "hostname" + ] + }, + "profile_settings": { + "type": "object", + "properties": { + "path": { + "type": "string" + }, + "bootstrap": { + "type": "string" + } + }, + "required": [ + "path" + ] + } + }, + "type": "object", + "allOf": [ + { + "$ref": "#/definitions/generic_settings" + }, + { + "type": "object", + "properties": { + "nodes": { + "type": "object", + "patternProperties": { + "[A-z][A-z0-9_-]*": { + "allOf": [ + { + "$ref": "#/definitions/generic_settings" + }, + { + "$ref": "#/definitions/node_settings" + }, + { + "type": "object", + "properties": { + "profiles": { + "type": "object", + "patternProperties": { + "[A-z][A-z0-9_-]*": { + "allOf": [ + { + "$ref": "#/definitions/generic_settings" + }, + { + "$ref": "#/definitions/profile_settings" + } + ] + } + }, + "additionalProperties": false + } + } + } + ] + } + }, + "additionalProperties": false + } + } + } + ] +} \ No newline at end of file -- cgit v1.3.1 From aabcf6b77d4159100a49b143cbb8da4bad194f14 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 5 Oct 2020 20:10:41 -0700 Subject: Improve schema a bit, fix flake locks for examples --- examples/simple/flake.lock | 10 ++++++---- examples/system/flake.lock | 10 ++++++---- interface/deploy.json | 44 +++++++++++++++++++++++--------------------- src/main.rs | 28 +++++++++++++++++++--------- src/utils/data.rs | 15 +++++++-------- 5 files changed, 61 insertions(+), 46 deletions(-) (limited to 'interface') diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock index 1e2fab0..8f4ca88 100644 --- a/examples/simple/flake.lock +++ b/examples/simple/flake.lock @@ -7,10 +7,12 @@ "utils": "utils" }, "locked": { - "lastModified": 1601952388, - "narHash": "sha256-g5t4JNUl4xmKfKmzo5yzligVNTBHOTgw+vkuebP4B/Q=", - "type": "git", - "url": "file:///home/notgne2/Dev/Serokell/deploy-rs" + "lastModified": 1601952901, + "narHash": "sha256-6U0JIlh6GLqkxdyUiVRbph9k1lVCtWLno2uM/Fd/ZzI=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "1de1ad5ff893bfcabdf2bfa20d8c93a8cdbb0156", + "type": "github" }, "original": { "owner": "serokell", diff --git a/examples/system/flake.lock b/examples/system/flake.lock index d3e489a..8f4ca88 100644 --- a/examples/system/flake.lock +++ b/examples/system/flake.lock @@ -7,10 +7,12 @@ "utils": "utils" }, "locked": { - "lastModified": 1601668691, - "narHash": "sha256-HvzPMsgSOQfCRoPtkwLRv09CkNjOsLHjcZtyHF+8Zbs=", - "type": "git", - "url": "file:///home/notgne2/Dev/Serokell/deploy-rs" + "lastModified": 1601952901, + "narHash": "sha256-6U0JIlh6GLqkxdyUiVRbph9k1lVCtWLno2uM/Fd/ZzI=", + "owner": "serokell", + "repo": "deploy-rs", + "rev": "1de1ad5ff893bfcabdf2bfa20d8c93a8cdbb0156", + "type": "github" }, "original": { "owner": "serokell", diff --git a/interface/deploy.json b/interface/deploy.json index aaa6534..310e926 100644 --- a/interface/deploy.json +++ b/interface/deploy.json @@ -31,6 +31,29 @@ "properties": { "hostname": { "type": "string" + }, + "profilesOrder": { + "type": "array", + "items": { + "type": "string" + }, + "uniqueItems": true + }, + "profiles": { + "type": "object", + "patternProperties": { + "[A-z][A-z0-9_-]*": { + "allOf": [ + { + "$ref": "#/definitions/generic_settings" + }, + { + "$ref": "#/definitions/profile_settings" + } + ] + } + }, + "additionalProperties": false } }, "required": [ @@ -70,27 +93,6 @@ }, { "$ref": "#/definitions/node_settings" - }, - { - "type": "object", - "properties": { - "profiles": { - "type": "object", - "patternProperties": { - "[A-z][A-z0-9_-]*": { - "allOf": [ - { - "$ref": "#/definitions/generic_settings" - }, - { - "$ref": "#/definitions/profile_settings" - } - ] - } - }, - "additionalProperties": false - } - } } ] } diff --git a/src/main.rs b/src/main.rs index 219c3e5..fb3fb66 100644 --- a/src/main.rs +++ b/src/main.rs @@ -65,17 +65,22 @@ async fn push_all_profiles( ) -> Result<(), Box> { info!("Pushing all profiles for `{}`", node_name); - let mut profiles_list: Vec<&str> = node.profiles_order.iter().map(|x| x.as_ref()).collect(); + let mut profiles_list: Vec<&str> = node + .node_settings + .profiles_order + .iter() + .map(|x| x.as_ref()) + .collect(); // Add any profiles which weren't in the provided order list - for profile_name in node.profiles.keys() { + for profile_name in node.node_settings.profiles.keys() { if !profiles_list.contains(&profile_name.as_str()) { profiles_list.push(&profile_name); } } for profile_name in profiles_list { - let profile = match node.profiles.get(profile_name) { + let profile = match node.node_settings.profiles.get(profile_name) { Some(x) => x, None => good_panic!("No profile was found named `{}`", profile_name), }; @@ -117,17 +122,22 @@ async fn deploy_all_profiles( ) -> Result<(), Box> { info!("Deploying all profiles for `{}`", node_name); - let mut profiles_list: Vec<&str> = node.profiles_order.iter().map(|x| x.as_ref()).collect(); + let mut profiles_list: Vec<&str> = node + .node_settings + .profiles_order + .iter() + .map(|x| x.as_ref()) + .collect(); // Add any profiles which weren't in the provided order list - for profile_name in node.profiles.keys() { + for profile_name in node.node_settings.profiles.keys() { if !profiles_list.contains(&profile_name.as_str()) { profiles_list.push(&profile_name); } } for profile_name in profiles_list { - let profile = match node.profiles.get(profile_name) { + let profile = match node.node_settings.profiles.get(profile_name) { Some(x) => x, None => good_panic!("No profile was found named `{}`", profile_name), }; @@ -203,8 +213,8 @@ async fn get_deployment_data( } let build_output = build_command - .stdout(Stdio::null()) - .stderr(Stdio::null()) + // .stdout(Stdio::null()) + // .stderr(Stdio::null()) .output() .await?; @@ -233,7 +243,7 @@ async fn run_deploy( Some(x) => x, None => good_panic!("No node was found named `{}`", node_name), }; - let profile = match node.profiles.get(profile_name) { + let profile = match node.node_settings.profiles.get(profile_name) { Some(x) => x, None => good_panic!("No profile was found named `{}`", profile_name), }; diff --git a/src/utils/data.rs b/src/utils/data.rs index de6adfc..371c82d 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -29,6 +29,13 @@ pub struct GenericSettings { #[derive(Deserialize, Debug, Clone)] pub struct NodeSettings { pub hostname: String, + pub profiles: HashMap, + #[serde( + skip_serializing_if = "Vec::is_empty", + default, + rename(deserialize = "profilesOrder") + )] + pub profiles_order: Vec, } #[derive(Deserialize, Debug, Clone)] @@ -51,14 +58,6 @@ pub struct Node { pub generic_settings: GenericSettings, #[serde(flatten)] pub node_settings: NodeSettings, - - pub profiles: HashMap, - #[serde( - skip_serializing_if = "Vec::is_empty", - default, - rename(deserialize = "profilesOrder") - )] - pub profiles_order: Vec, } #[derive(Deserialize, Debug, Clone)] -- cgit v1.3.1 From 518f7f5b4f1db83cab61941ab8887b0df76ce8d8 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Thu, 8 Oct 2020 18:13:26 -0700 Subject: Update documentation --- README.md | 88 +++++++++++++++++++++++++++++++++++++++++++++++++---- interface/README.md | 4 +-- src/main.rs | 14 --------- src/utils/mod.rs | 25 --------------- 4 files changed, 84 insertions(+), 47 deletions(-) (limited to 'interface') diff --git a/README.md b/README.md index 15877d1..e622250 100644 --- a/README.md +++ b/README.md @@ -6,20 +6,93 @@ SPDX-License-Identifier: MPL-2.0 # deploy-rs -A Simple multi-profile Nix-flake deploy tool. +A Simple, multi-profile Nix-flake deploy tool. -**This is very early development software, you should expect to find issues** +**This is very early development software, you should expect to find issues, and things will change** ## Usage +Basic usage: `deploy [options] `. -- `nix run github:serokell/deploy-rs your-flake#node.profile` -- `nix run github:serokell/deploy-rs your-flake#node` +The given flake can be just a source `my-flake`, specify the node to deploy `my-flake#my-node`, or specify a profile too `my-flake#my-node.my-profile`. + +You can try out this tool easily with `nix run`: - `nix run github:serokell/deploy-rs your-flake` +If your require a signing key to push closures to your server, specify the path to it in the `LOCAL_KEY` environment variable. + +Check out `deploy --help` for CLI flags! Remember to check there before making one-time changes to things like hostnames. + ## API -Example Nix expressions/configurations are in the [examples folder](./examples). +### Profile + +This is the core of how `deploy-rs` was designed, any number of these can run on a node, as any user (see further down for specifying user information). If you want to mimick the behaviour of traditional tools like NixOps or Morph, try just defining one `profile` called `system`, as root, containing a nixosSystem. + +```nix +{ + # ...generic options... (see below) + + # The command to bootstrap your profile, this is optional + bootstrap = "mkdir xyz"; + + # A derivation containing your required software, and a script to activate it in `${path}/activate` + # For ease of use, `deploy-rs` provides a function to easy all this required activation script to any derivation + path = deploy-rs.lib.x86_64-linux.setActivate pkgs.hello "./bin/hello"; +} +``` + +### Node + +This defines a single node/server, and the profiles you intend it to run. + +```nix +{ + # ...generic options... (see below) + + # The hostname of your server, don't worry, this can be overridden at runtime if needed + hostname = "my.server.gov"; + + # An optional list containing the order you want profiles to be deployed. + profilesOrder = [ "something" "system" ]; + + profiles = { + system = {}; # Definition shown above + something = {}; # Definition shown above + }; +} +``` + +### Deploy + +This is the top level attribute containing all of the options for this tool + +```nix +{ + # ...generic options... (see below) + + nodes = { + my-node = {}; # Definition shown above + another-node = {}; # Definition shown above + }; +} +``` + +#### Generic options + +This is a set of options that can be put in any of the above definitions, with the priority being `profile > node > deploy` + +```nix +{ + sshUser = "admin"; # This is the user that deploy-rs will use when connecting + user = "root"; # This is the user that the profile will be deployed to (will use sudo if not the same as above) + sshOpts = [ "-p" "2121" ]; # These are arguments that will be passed to SSH + fastConnection = false; # Fast connection to the node. If this is true, copy the whole closure instead of letting the node substitute + autoRollback = true; # If the previous profile should be re-activated if activation fails +} +``` + +A stronger definition of the schema is in the [interface directory](./interface), and full working examples Nix expressions/configurations are in the [examples folder](./examples). ## Idea @@ -30,8 +103,11 @@ This type of design (as opposed to more traditional tools like NixOps or morph) ## Things to work on - ~~Ordered profiles~~ -- Automatic rollbacks if one profile on node failed to deploy (partially implemented) +- ~~Automatic rollbacks~~ - UI (?) +- automatic kexec lustration of servers (maybe) +- Remote health checks +- Rollback on reconnection failure (technically, rollback if not reconnected to) ## About Serokell diff --git a/interface/README.md b/interface/README.md index c6b52bd..bfca01e 100644 --- a/interface/README.md +++ b/interface/README.md @@ -7,6 +7,7 @@ deploy ├── │   ├── │   ├── hostname + │   ├── profilesOrder │   └── profiles │   ├── │   │   ├── @@ -14,7 +15,6 @@ deploy │   │   └── path │   └── ... └── ... - ``` Where `` are all optional and can be one or multiple of: @@ -29,4 +29,4 @@ A formal definition for the structure can be found in [the JSON schema](./deploy For every profile of every node, arguments are merged with `` taking precedence over `` and `` taking precedence over top-level. -Values can be overridden for all the profiles deployed by setting environment variables with the same names as the profile, for example `sshUser=foobar nix run github:serokell/deploy .` will connect to all nodes as `foobar@.hostname`. +Certain read values can be overridden by supplying flags to the deploy binary, for example `deploy --auto-rollback true .` will enable automatic rollback for all nodes being deployed to, regardless of settings. \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 04d7868..b28a520 100644 --- a/src/main.rs +++ b/src/main.rs @@ -349,20 +349,6 @@ async fn main() -> Result<(), Box> { hostname: opts.hostname, }; - match (cmd_overrides.purity(), deploy_flake.node, deploy_flake.profile) { - (utils::OverridePurity::ErrorProfile, _, None) => good_panic!( - "You have specified an override not suitible for deploying to multiple profiles, please specify your target profile explicitly" - ), - (utils::OverridePurity::Error, None, _) => good_panic!( - "You have specified an override not suitible for deploying to multiple nodes, please specify your target node explicitly" - ), - - (utils::OverridePurity::Warn, None, _) => warn!( - "Certain overrides you have provided might be dangerous when used on multiple nodes or profiles, be cautious" - ), - _ => (), - }; - let supports_flakes = test_flake_support().await?; let data = diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bfdbc5e..97e4550 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -28,31 +28,6 @@ pub struct CmdOverrides { pub hostname: Option, } -pub enum OverridePurity { - ErrorProfile, - Error, - Warn, - Pure, -} - -impl CmdOverrides { - pub fn purity(&self) -> OverridePurity { - if self.profile_user.is_some() { - return OverridePurity::ErrorProfile; - } - - if self.hostname.is_some() || self.ssh_user.is_some() { - return OverridePurity::Error; - } - - if self.ssh_opts.is_some() || self.fast_connection.is_some() { - return OverridePurity::Warn; - } - - OverridePurity::Pure - } -} - #[derive(PartialEq, Debug)] pub struct DeployFlake<'a> { pub repo: &'a str, -- cgit v1.3.1 From db8301a45796cd919cbfa085f85ac6288e73a8db Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sat, 10 Oct 2020 10:31:55 -0700 Subject: Add profile path option to profiles --- interface/deploy.json | 3 +++ src/utils/data.rs | 2 ++ src/utils/mod.rs | 18 +++++++++++------- 3 files changed, 16 insertions(+), 7 deletions(-) (limited to 'interface') diff --git a/interface/deploy.json b/interface/deploy.json index 310e926..19da486 100644 --- a/interface/deploy.json +++ b/interface/deploy.json @@ -68,6 +68,9 @@ }, "bootstrap": { "type": "string" + }, + "profilePath": { + "type": "string" } }, "required": [ diff --git a/src/utils/data.rs b/src/utils/data.rs index 371c82d..f72f9a7 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -42,6 +42,8 @@ pub struct NodeSettings { pub struct ProfileSettings { pub path: String, pub bootstrap: Option, + #[serde(rename(deserialize = "profilePath"))] + pub profile_path: Option, } #[derive(Deserialize, Debug, Clone)] diff --git a/src/utils/mod.rs b/src/utils/mod.rs index bfdbc5e..51f977f 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -129,7 +129,7 @@ pub struct DeployData<'a> { pub struct DeployDefs<'a> { pub ssh_user: Cow<'a, str>, pub profile_user: Cow<'a, str>, - pub profile_path: String, + pub profile_path: Cow<'a, str>, pub current_exe: PathBuf, pub sudo: Option, } @@ -153,12 +153,16 @@ impl<'a> DeployData<'a> { }, }; - let profile_path = match &profile_user[..] { - "root" => format!("/nix/var/nix/profiles/{}", self.profile_name), - _ => format!( - "/nix/var/nix/profiles/per-user/{}/{}", - profile_user, self.profile_name - ), + let profile_path: Cow = match self.profile.profile_settings.profile_path { + None => match &profile_user[..] { + "root" => format!("/nix/var/nix/profiles/{}", self.profile_name).into(), + _ => format!( + "/nix/var/nix/profiles/per-user/{}/{}", + profile_user, self.profile_name + ) + .into(), + }, + Some(ref x) => x.into(), }; let sudo: Option = match self.merged_settings.user { -- cgit v1.3.1 From 867438bfc011e63b633b9d6fa2b0edb3367ea253 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sat, 10 Oct 2020 10:42:14 -0700 Subject: Document `profilePath` option --- README.md | 3 +++ interface/README.md | 1 + 2 files changed, 4 insertions(+) (limited to 'interface') diff --git a/README.md b/README.md index 2710183..cbc312d 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ This is the core of how `deploy-rs` was designed, any number of these can run on # For ease of use, `deploy-rs` provides a function to easy all this required activation script to any derivation path = deploy-rs.lib.x86_64-linux.setActivate pkgs.hello "./bin/hello"; + # An optional path to where your profile should be installed to, this is useful if you want to use a common profile name across multiple users, but would have conflicts in your node's profile list. + profilePath = "/nix/var/nix/profiles/per-user/someuser/someprofile"; + # ...generic options... (see lower section) } ``` diff --git a/interface/README.md b/interface/README.md index bfca01e..99afeb2 100644 --- a/interface/README.md +++ b/interface/README.md @@ -12,6 +12,7 @@ deploy │   ├── │   │   ├── │   │   ├── bootstrap + │   │   ├── profilePath │   │   └── path │   └── ... └── ... -- cgit v1.3.1 From b2326d8694465718024e63c691fe9920c416489e Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 11 Oct 2020 15:23:58 -0700 Subject: Add missing license information --- default.nix | 4 ++++ interface/README.md | 6 ++++++ interface/deploy.json.license | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 interface/deploy.json.license (limited to 'interface') diff --git a/default.nix b/default.nix index 873ece4..73df194 100644 --- a/default.nix +++ b/default.nix @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + (import ( let diff --git a/interface/README.md b/interface/README.md index 99afeb2..f61a69f 100644 --- a/interface/README.md +++ b/interface/README.md @@ -1,3 +1,9 @@ + + A flake must have a `deploy` output with the following structure: ``` diff --git a/interface/deploy.json.license b/interface/deploy.json.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/interface/deploy.json.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file -- cgit v1.3.1 From ea717911bac5ff29d730d80d4b774fe17ed1e851 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 13 Oct 2020 19:06:40 -0700 Subject: Clean up some CLI arguments, make magic rollback optional --- interface/deploy.json | 9 ++++++ src/activate.rs | 39 +++++++++++++++++------- src/main.rs | 73 ++++++++++++++++++++++---------------------- src/utils/data.rs | 16 +++++----- src/utils/deploy.rs | 84 ++++++++++++++++++++++++++++++--------------------- src/utils/mod.rs | 10 ++++-- src/utils/push.rs | 2 +- 7 files changed, 141 insertions(+), 92 deletions(-) (limited to 'interface') diff --git a/interface/deploy.json b/interface/deploy.json index 19da486..93bacb8 100644 --- a/interface/deploy.json +++ b/interface/deploy.json @@ -23,6 +23,15 @@ }, "autoRollback": { "type": "boolean" + }, + "magicRollback": { + "type": "boolean" + }, + "confirmTimeout": { + "type": "int" + }, + "tempPath": { + "type": "integer" } } }, diff --git a/src/activate.rs b/src/activate.rs index ce6b286..55ceb27 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -34,8 +34,18 @@ mod utils; struct Opts { profile_path: String, closure: String, + + /// Temp path for any temporary files that may be needed during activation + #[clap(long)] temp_path: String, - max_time: u16, + + /// Maximum time to wait for confirmation after activation + #[clap(long)] + confirm_timeout: u16, + + /// Wait for confirmation after deployment and rollback if not confirmed + #[clap(long)] + magic_rollback: bool, /// Command for bootstrapping #[clap(long)] @@ -139,7 +149,7 @@ async fn deactivate_on_err(profile_path: &str, r: Result pub async fn activation_confirmation( profile_path: String, temp_path: String, - max_time: u16, + confirm_timeout: u16, closure: String, ) -> Result<(), Box> { let lock_hash = &closure[11 /* /nix/store/ */ ..]; @@ -172,7 +182,8 @@ pub async fn activation_confirmation( &profile_path, deactivate_on_err( &profile_path, - timeout(Duration::from_secs(max_time as u64), stream.next()).await, + timeout(Duration::from_secs(confirm_timeout as u64), stream.next()) + .await, ) .await .ok_or("Watcher ended prematurely"), @@ -201,7 +212,8 @@ pub async fn activate( bootstrap_cmd: Option, auto_rollback: bool, temp_path: String, - max_time: u16, + confirm_timeout: u16, + magic_rollback: bool, ) -> Result<(), Box> { info!("Activating profile"); @@ -249,13 +261,17 @@ pub async fn activate( _ => (), } - info!("Activation succeeded, now performing post-activation checks"); + info!("Activation succeeded!"); - deactivate_on_err( - &profile_path, - activation_confirmation(profile_path.clone(), temp_path, max_time, closure).await, - ) - .await; + if magic_rollback { + info!("Performing activation confirmation steps"); + deactivate_on_err( + &profile_path, + activation_confirmation(profile_path.clone(), temp_path, confirm_timeout, closure) + .await, + ) + .await; + } Ok(()) } @@ -276,7 +292,8 @@ async fn main() -> Result<(), Box> { opts.bootstrap_cmd, opts.auto_rollback, opts.temp_path, - opts.max_time, + opts.confirm_timeout, + opts.magic_rollback, ) .await?; diff --git a/src/main.rs b/src/main.rs index b28a520..cedf684 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,9 +51,15 @@ struct Opts { /// Override hostname used for the node #[clap(long)] hostname: Option, - /// Skip pushing step (useful for local testing) - #[clap(short, long)] - skip_push: bool, + /// Make activation wait for confirmation, or roll back after a period of time + #[clap(long)] + magic_rollback: Option, + /// How long activation should wait for confirmation (if using magic-rollback) + #[clap(long)] + confirm_timeout: Option, + /// Where to store temporary files (only used by magic-rollback) + #[clap(long)] + temp_path: Option, } #[inline] @@ -238,7 +244,6 @@ async fn run_deploy( data: utils::data::Data, supports_flakes: bool, check_sigs: bool, - skip_push: bool, cmd_overrides: utils::CmdOverrides, ) -> Result<(), Box> { match (deploy_flake.node, deploy_flake.profile) { @@ -263,16 +268,14 @@ async fn run_deploy( let deploy_defs = deploy_data.defs(); - if !skip_push { - utils::push::push_profile( - supports_flakes, - check_sigs, - deploy_flake.repo, - &deploy_data, - &deploy_defs, - ) - .await?; - } + utils::push::push_profile( + supports_flakes, + check_sigs, + deploy_flake.repo, + &deploy_data, + &deploy_defs, + ) + .await?; utils::deploy::deploy_profile(&deploy_data, &deploy_defs).await?; } @@ -282,7 +285,23 @@ async fn run_deploy( None => good_panic!("No node was found named `{}`", node_name), }; - if !skip_push { + push_all_profiles( + node, + node_name, + supports_flakes, + deploy_flake.repo, + &data.generic_settings, + check_sigs, + &cmd_overrides, + ) + .await?; + + deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides).await?; + } + (None, None) => { + info!("Deploying all profiles on all nodes"); + + for (node_name, node) in &data.nodes { push_all_profiles( node, node_name, @@ -295,26 +314,6 @@ async fn run_deploy( .await?; } - deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides).await?; - } - (None, None) => { - info!("Deploying all profiles on all nodes"); - - if !skip_push { - for (node_name, node) in &data.nodes { - push_all_profiles( - node, - node_name, - supports_flakes, - deploy_flake.repo, - &data.generic_settings, - check_sigs, - &cmd_overrides, - ) - .await?; - } - } - for (node_name, node) in &data.nodes { deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides) .await?; @@ -347,6 +346,9 @@ async fn main() -> Result<(), Box> { fast_connection: opts.fast_connection, auto_rollback: opts.auto_rollback, hostname: opts.hostname, + magic_rollback: opts.magic_rollback, + temp_path: opts.temp_path, + confirm_timeout: opts.confirm_timeout, }; let supports_flakes = test_flake_support().await?; @@ -359,7 +361,6 @@ async fn main() -> Result<(), Box> { data, supports_flakes, opts.checksigs, - opts.skip_push, cmd_overrides, ) .await?; diff --git a/src/utils/data.rs b/src/utils/data.rs index 351b9ae..5c58e3b 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -19,11 +19,15 @@ pub struct GenericSettings { #[merge(strategy = merge::vec::append)] pub ssh_opts: Vec, #[serde(rename(deserialize = "fastConnection"), default)] - #[merge(strategy = merge::bool::overwrite_false)] - pub fast_connection: bool, + pub fast_connection: Option, #[serde(rename(deserialize = "autoRollback"), default)] - #[merge(strategy = merge::bool::overwrite_false)] - pub auto_rollback: bool, + pub auto_rollback: Option, + #[serde(rename(deserialize = "confirmTimeout"))] + pub confirm_timeout: Option, + #[serde(rename(deserialize = "tempPath"))] + pub temp_path: Option, + #[serde(rename(deserialize = "magicRollback"))] + pub magic_rollback: Option, } #[derive(Deserialize, Debug, Clone)] @@ -44,10 +48,6 @@ pub struct ProfileSettings { pub bootstrap: Option, #[serde(rename(deserialize = "profilePath"))] pub profile_path: Option, - #[serde(rename(deserialize = "maxTime"))] - pub max_time: Option, - #[serde(rename(deserialize = "tempPath"))] - pub temp_path: Option, } #[derive(Deserialize, Debug, Clone)] diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index e3493ba..9b2f685 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -13,15 +13,20 @@ fn build_activate_command( bootstrap_cmd: &Option, auto_rollback: bool, temp_path: &Cow, - max_time: u16, + confirm_timeout: u16, + magic_rollback: bool, ) -> String { let mut self_activate_command = format!( - "{} '{}' '{}' {} {}", - activate_path_str, profile_path, closure, temp_path, max_time + "{} '{}' '{}' --temp-path {} --confirm-timeout {}", + activate_path_str, profile_path, closure, temp_path, confirm_timeout ); - if let Some(sudo_cmd) = &sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); + if magic_rollback { + self_activate_command = format!("{} --magic-rollback", self_activate_command); + } + + if auto_rollback { + self_activate_command = format!("{} --auto-rollback", self_activate_command); } if let Some(ref bootstrap_cmd) = bootstrap_cmd { @@ -31,8 +36,8 @@ fn build_activate_command( ); } - if auto_rollback { - self_activate_command = format!("{} --auto-rollback", self_activate_command); + if let Some(sudo_cmd) = &sudo { + self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); } self_activate_command @@ -47,7 +52,8 @@ fn test_activation_command_builder() { let bootstrap_cmd = None; let auto_rollback = true; let temp_path = &"/tmp/deploy-rs".into(); - let max_time = 30; + let confirm_timeout = 30; + let magic_rollback = true; assert_eq!( build_activate_command( @@ -58,9 +64,10 @@ fn test_activation_command_builder() { &bootstrap_cmd, auto_rollback, temp_path, - max_time + confirm_timeout, + magic_rollback ), - "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' /tmp/deploy-rs 30 --auto-rollback" + "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' --temp-path /tmp/deploy-rs --confirm-timeout 30 --magic-rollback --auto-rollback" .to_string(), ); } @@ -76,12 +83,16 @@ pub async fn deploy_profile( let activate_path_str = super::deploy_path_to_activate_path_str(&deploy_defs.current_exe)?; - let temp_path: Cow = match &deploy_data.profile.profile_settings.temp_path { + let temp_path: Cow = match &deploy_data.merged_settings.temp_path { Some(x) => x.into(), None => "/tmp/deploy-rs".into(), }; - let max_time = deploy_data.profile.profile_settings.max_time.unwrap_or(30); + let confirm_timeout = deploy_data.merged_settings.confirm_timeout.unwrap_or(30); + + let magic_rollback = deploy_data.merged_settings.magic_rollback.unwrap_or(false); + + let auto_rollback = deploy_data.merged_settings.auto_rollback.unwrap_or(true); let self_activate_command = build_activate_command( activate_path_str, @@ -89,9 +100,10 @@ pub async fn deploy_profile( &deploy_defs.profile_path, &deploy_data.profile.profile_settings.path, &deploy_data.profile.profile_settings.bootstrap, - deploy_data.merged_settings.auto_rollback, + auto_rollback, &temp_path, - max_time, + confirm_timeout, + magic_rollback, ); let hostname = match deploy_data.cmd_overrides.hostname { @@ -112,33 +124,37 @@ pub async fn deploy_profile( good_panic!("Activation over SSH failed"); } - info!("Success, attempting to connect to the node to confirm deployment"); + info!("Success activating!"); - let mut c = Command::new("ssh"); - let mut ssh_confirm_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); + if magic_rollback { + info!("Attempting to confirm activation"); - for ssh_opt in &deploy_data.merged_settings.ssh_opts { - ssh_confirm_command = ssh_confirm_command.arg(ssh_opt); - } + let mut c = Command::new("ssh"); + let mut ssh_confirm_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); - let lock_hash = &deploy_data.profile.profile_settings.path[11 /* /nix/store/ */ ..]; - let lock_path = format!("{}/activating-{}", temp_path, lock_hash); + for ssh_opt in &deploy_data.merged_settings.ssh_opts { + ssh_confirm_command = ssh_confirm_command.arg(ssh_opt); + } - let mut confirm_command = format!("rm {}", lock_path); - if let Some(sudo_cmd) = &deploy_defs.sudo { - confirm_command = format!("{} {}", sudo_cmd, confirm_command); - } + let lock_hash = &deploy_data.profile.profile_settings.path[11 /* /nix/store/ */ ..]; + let lock_path = format!("{}/activating-{}", temp_path, lock_hash); - let ssh_exit_status = ssh_confirm_command.arg(confirm_command).status().await?; + let mut confirm_command = format!("rm {}", lock_path); + if let Some(sudo_cmd) = &deploy_defs.sudo { + confirm_command = format!("{} {}", sudo_cmd, confirm_command); + } - if !ssh_exit_status.success() { - good_panic!( - "Failed to confirm deployment, the node will roll back in <{} seconds", - max_time - ); - } + let ssh_exit_status = ssh_confirm_command.arg(confirm_command).status().await?; - info!("Deployment confirmed."); + if !ssh_exit_status.success() { + good_panic!( + "Failed to confirm deployment, the node will roll back in <{} seconds", + confirm_timeout + ); + } + + info!("Deployment confirmed."); + } Ok(()) } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 672a9ba..a0e62e1 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -26,6 +26,9 @@ pub struct CmdOverrides { pub fast_connection: Option, pub auto_rollback: Option, pub hostname: Option, + pub magic_rollback: Option, + pub temp_path: Option, + pub confirm_timeout: Option, } #[derive(PartialEq, Debug)] @@ -184,10 +187,13 @@ pub fn make_deploy_data<'a, 's>( merged_settings.ssh_opts = ssh_opts.split(' ').map(|x| x.to_owned()).collect(); } if let Some(fast_connection) = cmd_overrides.fast_connection { - merged_settings.fast_connection = fast_connection; + merged_settings.fast_connection = Some(fast_connection); } if let Some(auto_rollback) = cmd_overrides.auto_rollback { - merged_settings.auto_rollback = auto_rollback; + merged_settings.auto_rollback = Some(auto_rollback); + } + if let Some(magic_rollback) = cmd_overrides.magic_rollback { + merged_settings.magic_rollback = Some(magic_rollback); } Ok(DeployData { diff --git a/src/utils/push.rs b/src/utils/push.rs index 3f48d68..f80f9f8 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -80,7 +80,7 @@ pub async fn push_profile( let mut copy_command_ = Command::new("nix"); let mut copy_command = copy_command_.arg("copy"); - if deploy_data.merged_settings.fast_connection { + if let Some(true) = deploy_data.merged_settings.fast_connection { copy_command = copy_command.arg("--substitute-on-destination"); } -- cgit v1.3.1 From c7b1e9d9617ef02f974f56a47f4a0ee3fbf4020e Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 13 Oct 2020 19:10:07 -0700 Subject: Fix json schema --- interface/deploy.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'interface') diff --git a/interface/deploy.json b/interface/deploy.json index 93bacb8..fa45e50 100644 --- a/interface/deploy.json +++ b/interface/deploy.json @@ -28,10 +28,10 @@ "type": "boolean" }, "confirmTimeout": { - "type": "int" + "type": "integer" }, "tempPath": { - "type": "integer" + "type": "string" } } }, -- cgit v1.3.1