From 76dbef54af3bbfbf81edd8a45f6ba8dea0db47f9 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 10:48:21 -0700 Subject: stuff --- Cargo.toml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 Cargo.toml (limited to 'Cargo.toml') diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..6d0ef17 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "deploy-rs" +version = "0.1.0" +authors = ["notgne2 "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +clap = "3.0.0-beta.2" +tokio = { version = "0.2.22", features = [ "full" ] } +serde_json = "1.0.48" +serde_derive = "1.0.104" +serde = "1.0.104" +merge = "0.1.0" +whoami = "0.9.0" +log = "0.4" +pretty_env_logger = "0.4" -- cgit v1.3.1 From 239d0f8999b47e9e76589ee1fa2d9f3459c47335 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 15:45:53 -0700 Subject: use separate binary for activation, more cleanup --- Cargo.toml | 8 ++ flake.nix | 2 +- src/activate.rs | 54 +++++++++++++ src/main.rs | 220 +++++++++++++++++++++------------------------------- src/utils/data.rs | 10 ++- src/utils/deploy.rs | 17 +++- src/utils/push.rs | 2 +- 7 files changed, 172 insertions(+), 141 deletions(-) create mode 100644 src/activate.rs (limited to 'Cargo.toml') diff --git a/Cargo.toml b/Cargo.toml index 6d0ef17..035191f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,11 @@ merge = "0.1.0" whoami = "0.9.0" log = "0.4" pretty_env_logger = "0.4" + +[[bin]] +name = "deploy" +path = "src/main.rs" + +[[bin]] +name = "activate" +path = "src/activate.rs" diff --git a/flake.nix b/flake.nix index 2efb943..acc3f18 100644 --- a/flake.nix +++ b/flake.nix @@ -16,7 +16,7 @@ defaultApp = { type = "app"; - program = "${self.defaultPackage."${system}"}/bin/deploy-rs"; + program = "${self.defaultPackage."${system}"}/bin/deploy"; }; }); } diff --git a/src/activate.rs b/src/activate.rs new file mode 100644 index 0000000..c446ee0 --- /dev/null +++ b/src/activate.rs @@ -0,0 +1,54 @@ +use clap::Clap; + +extern crate pretty_env_logger; +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde_derive; + +#[macro_use] +mod utils; + +/// Activation portion of the simple Rust Nix deploy tool +#[derive(Clap, Debug)] +#[clap(version = "1.0", author = "notgne2 ")] +struct Opts { + profile_path: String, + closure: String, + + /// Command for activating the given profile + #[clap(long)] + activate_cmd: Option, + + /// Command for bootstrapping + #[clap(long)] + bootstrap_cmd: Option, + + /// Auto rollback if failure + #[clap(long)] + auto_rollback: bool, +} + + +#[tokio::main] +async fn main() -> Result<(), Box> { + if std::env::var("DEPLOY_LOG").is_err() { + std::env::set_var("DEPLOY_LOG", "info"); + } + + pretty_env_logger::init_custom_env("DEPLOY_LOG"); + + let opts: Opts = Opts::parse(); + + utils::activate::activate( + opts.profile_path, + opts.closure, + opts.activate_cmd, + opts.bootstrap_cmd, + opts.auto_rollback, + ) + .await?; + + Ok(()) +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index e084f0f..6dcb885 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,17 +19,6 @@ mod utils; #[derive(Clap, Debug)] #[clap(version = "1.0", author = "notgne2 ")] struct Opts { - /// Log verbosity - #[clap(short, long, parse(from_occurrences))] - verbose: i32, - - #[clap(subcommand)] - subcmd: SubCommand, -} - -/// Deploy profiles -#[derive(Clap, Debug)] -struct DeployOpts { /// The flake to deploy #[clap(default_value = ".")] flake: String, @@ -38,31 +27,6 @@ struct DeployOpts { checksigs: bool, } -/// Activate a profile on your current machine -#[derive(Clap, Debug)] -struct ActivateOpts { - profile_path: String, - closure: String, - - /// Command for activating the given profile - #[clap(short, long)] - activate_cmd: Option, - - /// Command for bootstrapping - #[clap(short, long)] - bootstrap_cmd: Option, - - /// Auto rollback if failure - #[clap(short, long)] - auto_rollback: bool, -} - -#[derive(Clap, Debug)] -enum SubCommand { - Deploy(DeployOpts), - Activate(ActivateOpts), -} - #[inline] async fn push_all_profiles( node: &utils::data::Node, @@ -150,6 +114,7 @@ async fn deploy_all_profiles( node_name, &merged_settings, &deploy_data, + merged_settings.auto_rollback, ) .await?; } @@ -157,6 +122,7 @@ async fn deploy_all_profiles( Ok(()) } +/// Returns if the available Nix installation supports flakes async fn test_flake_support() -> Result> { Ok(Command::new("nix") .arg("eval") @@ -169,6 +135,7 @@ async fn test_flake_support() -> Result> { .success()) } +/// Evaluates the Nix in the given `repo` and return the processed Data from it async fn get_deployment_data( supports_flakes: bool, repo: &str, @@ -216,108 +183,95 @@ async fn main() -> Result<(), Box> { let opts: Opts = Opts::parse(); - match opts.subcmd { - SubCommand::Deploy(deploy_opts) => { - let deploy_flake = utils::parse_flake(deploy_opts.flake.as_str()); - - let supports_flakes = test_flake_support().await?; - - let data = get_deployment_data(supports_flakes, deploy_flake.repo).await?; - - match (deploy_flake.node, deploy_flake.profile) { - (Some(node_name), Some(profile_name)) => { - let node = match data.nodes.get(node_name) { - Some(x) => x, - None => good_panic!("No node was found named `{}`", node_name), - }; - let profile = match node.profiles.get(profile_name) { - Some(x) => x, - None => good_panic!("No profile was found named `{}`", profile_name), - }; - - let mut merged_settings = data.generic_settings.clone(); - merged_settings.merge(node.generic_settings.clone()); - merged_settings.merge(profile.generic_settings.clone()); - - let deploy_data = - utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - - utils::push::push_profile( - profile, - profile_name, - node, - node_name, - supports_flakes, - deploy_opts.checksigs, - deploy_flake.repo, - &merged_settings, - &deploy_data, - ) - .await?; - - utils::deploy::deploy_profile( - profile, - profile_name, - node, - node_name, - &merged_settings, - &deploy_data, - ) - .await?; - } - (Some(node_name), None) => { - let node = match data.nodes.get(node_name) { - Some(x) => x, - None => good_panic!("No node was found named `{}`", node_name), - }; - - push_all_profiles( - node, - node_name, - supports_flakes, - deploy_flake.repo, - &data.generic_settings, - deploy_opts.checksigs, - ) - .await?; - - deploy_all_profiles(node, node_name, &data.generic_settings).await?; - } - (None, None) => { - info!("Deploying all profiles on all nodes"); - - for (node_name, node) in &data.nodes { - push_all_profiles( - node, - node_name, - supports_flakes, - deploy_flake.repo, - &data.generic_settings, - deploy_opts.checksigs, - ) - .await?; - } - - for (node_name, node) in &data.nodes { - deploy_all_profiles(node, node_name, &data.generic_settings).await?; - } - } - (None, Some(_)) => good_panic!( - "Profile provided without a node, this is not (currently) supported" - ), + let deploy_flake = utils::parse_flake(opts.flake.as_str()); + + let supports_flakes = test_flake_support().await?; + + let data = get_deployment_data(supports_flakes, deploy_flake.repo).await?; + + match (deploy_flake.node, deploy_flake.profile) { + (Some(node_name), Some(profile_name)) => { + let node = match data.nodes.get(node_name) { + Some(x) => x, + None => good_panic!("No node was found named `{}`", node_name), + }; + let profile = match node.profiles.get(profile_name) { + Some(x) => x, + None => good_panic!("No profile was found named `{}`", profile_name), }; + + let mut merged_settings = data.generic_settings.clone(); + merged_settings.merge(node.generic_settings.clone()); + merged_settings.merge(profile.generic_settings.clone()); + + let deploy_data = + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; + + utils::push::push_profile( + profile, + profile_name, + node, + node_name, + supports_flakes, + opts.checksigs, + deploy_flake.repo, + &merged_settings, + &deploy_data, + ) + .await?; + + utils::deploy::deploy_profile( + profile, + profile_name, + node, + node_name, + &merged_settings, + &deploy_data, + merged_settings.auto_rollback, + ) + .await?; } - SubCommand::Activate(activate_opts) => { - utils::activate::activate( - activate_opts.profile_path, - activate_opts.closure, - activate_opts.activate_cmd, - activate_opts.bootstrap_cmd, - activate_opts.auto_rollback, + (Some(node_name), None) => { + let node = match data.nodes.get(node_name) { + Some(x) => x, + None => good_panic!("No node was found named `{}`", node_name), + }; + + push_all_profiles( + node, + node_name, + supports_flakes, + deploy_flake.repo, + &data.generic_settings, + opts.checksigs, ) .await?; + + deploy_all_profiles(node, node_name, &data.generic_settings).await?; } - } + (None, None) => { + info!("Deploying all profiles on all nodes"); + + for (node_name, node) in &data.nodes { + push_all_profiles( + node, + node_name, + supports_flakes, + deploy_flake.repo, + &data.generic_settings, + opts.checksigs, + ) + .await?; + } + + for (node_name, node) in &data.nodes { + deploy_all_profiles(node, node_name, &data.generic_settings).await?; + } + } + (None, Some(_)) => { + good_panic!("Profile provided without a node, this is not (currently) supported") + } + }; Ok(()) } diff --git a/src/utils/data.rs b/src/utils/data.rs index b28b6cd..0753508 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -14,10 +14,12 @@ pub struct GenericSettings { )] #[merge(strategy = merge::vec::append)] pub ssh_opts: Vec, - #[serde(rename(deserialize = "fastConnection"))] - pub fast_connection: Option, - #[serde(rename(deserialize = "autoRollback"))] - pub auto_rollback: Option, + #[serde(rename(deserialize = "fastConnection"), default)] + #[merge(strategy = merge::bool::overwrite_false)] + pub fast_connection: bool, + #[serde(rename(deserialize = "autoRollback"), default)] + #[merge(strategy = merge::bool::overwrite_false)] + pub auto_rollback: bool, } #[derive(Deserialize, Debug, Clone)] diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 900743c..42bd0b4 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -9,6 +9,7 @@ pub async fn deploy_profile( node_name: &str, merged_settings: &data::GenericSettings, deploy_data: &super::DeployData<'_>, + auto_rollback: bool, ) -> Result<(), Box> { info!( "Activating profile `{}` for node `{}`", @@ -16,8 +17,16 @@ pub async fn deploy_profile( ); let mut self_activate_command = format!( - "{} activate '{}' '{}'", - deploy_data.current_exe.as_path().to_str().unwrap(), + "{} '{}' '{}'", + deploy_data + .current_exe + .as_path() + .parent() + .unwrap() + .to_str() + .unwrap() + .to_owned() + + "/activate", deploy_data.profile_path, profile.profile_settings.path, ); @@ -40,6 +49,10 @@ pub async fn deploy_profile( ); } + if auto_rollback { + self_activate_command = format!("{} --auto-rollback", self_activate_command); + } + let mut c = Command::new("ssh"); let mut ssh_command = c.arg(format!( "ssh://{}@{}", diff --git a/src/utils/push.rs b/src/utils/push.rs index 54ae013..0e1b9ba 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -74,7 +74,7 @@ pub async fn push_profile( let mut copy_command_ = Command::new("nix"); let mut copy_command = copy_command_.arg("copy"); - if let Some(true) = merged_settings.fast_connection { + if merged_settings.fast_connection { copy_command = copy_command.arg("--substitute-on-destination"); } -- cgit v1.3.1 From 8d21dd335e5259dadf832a5d1a7c72b9dd1f4400 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 29 Sep 2020 15:10:06 -0700 Subject: Add license information, reformat Nix files, clean up --- .gitignore | 4 + .reuse/dep5 | 10 ++ Cargo.lock.license | 3 + Cargo.toml | 6 +- LICENSE | 312 ++++++++++++++++++++++++++++++++++++ LICENSES/MPL-2.0.txt | 312 ++++++++++++++++++++++++++++++++++++ README.md | 25 ++- examples/simple/flake.lock.license | 3 + examples/simple/flake.nix | 16 +- examples/system/README.md | 6 + examples/system/bare.nix | 4 + examples/system/common.nix | 4 + examples/system/configuration.nix | 6 +- examples/system/flake.lock.license | 3 + examples/system/flake.nix | 17 +- examples/system/hello.nix | 9 +- examples/system/nix-pub.pem.license | 3 + examples/system/nix.key.license | 3 + flake.lock.license | 3 + flake.nix | 7 +- src/activate.rs | 4 + src/main.rs | 6 +- src/utils/data.rs | 4 + src/utils/deploy.rs | 4 + src/utils/mod.rs | 4 + src/utils/push.rs | 4 + 26 files changed, 757 insertions(+), 25 deletions(-) create mode 100644 .reuse/dep5 create mode 100644 Cargo.lock.license create mode 100644 LICENSE create mode 100644 LICENSES/MPL-2.0.txt create mode 100644 examples/simple/flake.lock.license create mode 100644 examples/system/flake.lock.license create mode 100644 examples/system/nix-pub.pem.license create mode 100644 examples/system/nix.key.license create mode 100644 flake.lock.license (limited to 'Cargo.toml') diff --git a/.gitignore b/.gitignore index 5a39c3d..58f5ece 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + /target result /examples/system/bare-system.qcow2 \ No newline at end of file diff --git a/.reuse/dep5 b/.reuse/dep5 new file mode 100644 index 0000000..d5d6256 --- /dev/null +++ b/.reuse/dep5 @@ -0,0 +1,10 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: deploy-rs +Upstream-Contact: Serokell +Source: https://github.com/serokell/deploy-rs + +# Sample paragraph, commented out: +# +# Files: src/* +# Copyright: $YEAR $NAME <$CONTACT> +# License: ... diff --git a/Cargo.lock.license b/Cargo.lock.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/Cargo.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index 035191f..07b4144 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,11 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + [package] name = "deploy-rs" version = "0.1.0" -authors = ["notgne2 "] +authors = ["Serokell "] edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..09f2798 --- /dev/null +++ b/LICENSE @@ -0,0 +1,312 @@ +Mozilla Public License Version 2.0 + + 1. Definitions + +1.1. "Contributor" means each individual or legal entity that creates, contributes +to the creation of, or owns Covered Software. + +1.2. "Contributor Version" means the combination of the Contributions of others +(if any) used by a Contributor and that particular Contributor's Contribution. + + 1.3. "Contribution" means Covered Software of a particular Contributor. + +1.4. "Covered Software" means Source Code Form to which the initial Contributor +has attached the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case including portions +thereof. + + 1.5. "Incompatible With Secondary Licenses" means + +(a) that the initial Contributor has attached the notice described in Exhibit +B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of version +1.1 or earlier of the License, but not also under the terms of a Secondary +License. + +1.6. "Executable Form" means any form of the work other than Source Code Form. + +1.7. "Larger Work" means a work that combines Covered Software with other +material, in a separate file or files, that is not Covered Software. + + 1.8. "License" means this document. + +1.9. "Licensable" means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and all of the +rights conveyed by this License. + + 1.10. "Modifications" means any of the following: + +(a) any file in Source Code Form that results from an addition to, deletion +from, or modification of the contents of Covered Software; or + +(b) any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor means any patent claim(s), including +without limitation, method, process, and apparatus claims, in any patent Licensable +by such Contributor that would be infringed, but for the grant of the License, +by the making, using, selling, offering for sale, having made, import, or +transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" means either the GNU General Public License, Version +2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") means an individual or a legal entity exercising rights +under this License. For legal entities, "You" includes any entity that controls, +is controlled by, or is under common control with You. For purposes of this +definition, "control" means (a) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, +or (b) ownership of more than fifty percent (50%) of the outstanding shares +or beneficial ownership of such entity. + + 2. License Grants and Conditions + + 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive +license: + +(a) under intellectual property rights (other than patent or trademark) Licensable +by such Contributor to use, reproduce, make available, modify, display, perform, +distribute, and otherwise exploit its Contributions, either on an unmodified +basis, with Modifications, or as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer for +sale, have made, import, and otherwise transfer either its Contributions or +its Contributor Version. + + 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution become +effective for each Contribution on the date the Contributor first distributes +such Contribution. + + 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under this +License. No additional rights or licenses will be implied from the distribution +or licensing of Covered Software under this License. Notwithstanding Section +2.1(b) above, no patent license is granted by a Contributor: + +(a) for any code that a Contributor has removed from Covered Software; or + +(b) for infringements caused by: (i) Your and any other third party's modifications +of Covered Software, or (ii) the combination of its Contributions with other +software (except as part of its Contributor Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of its +Contributions. + +This License does not grant any rights in the trademarks, service marks, or +logos of any Contributor (except as may be necessary to comply with the notice +requirements in Section 3.4). + + 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to distribute +the Covered Software under a subsequent version of this License (see Section +10.2) or under the terms of a Secondary License (if permitted under the terms +of Section 3.3). + + 2.5. Representation + +Each Contributor represents that the Contributor believes its Contributions +are its original creation(s) or it has sufficient rights to grant the rights +to its Contributions conveyed by this License. + + 2.6. Fair Use + +This License is not intended to limit any rights You have under applicable +copyright doctrines of fair use, fair dealing, or other equivalents. + + 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in +Section 2.1. + + 3. Responsibilities + + 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any Modifications +that You create or to which You contribute, must be under the terms of this +License. You must inform recipients that the Source Code Form of the Covered +Software is governed by the terms of this License, and how they can obtain +a copy of this License. You may not attempt to alter or restrict the recipients' +rights in the Source Code Form. + + 3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code Form, +as described in Section 3.1, and You must inform recipients of the Executable +Form how they can obtain a copy of such Source Code Form by reasonable means +in a timely manner, at a charge no more than the cost of distribution to the +recipient; and + +(b) You may distribute such Executable Form under the terms of this License, +or sublicense it under different terms, provided that the license for the +Executable Form does not attempt to limit or alter the recipients' rights +in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, provided +that You also comply with the requirements of this License for the Covered +Software. If the Larger Work is a combination of Covered Software with a work +governed by one or more Secondary Licenses, and the Covered Software is not +Incompatible With Secondary Licenses, this License permits You to additionally +distribute such Covered Software under the terms of such Secondary License(s), +so that the recipient of the Larger Work may, at their option, further distribute +the Covered Software under the terms of either this License or such Secondary +License(s). + + 3.4. Notices + +You may not remove or alter the substance of any license notices (including +copyright notices, patent notices, disclaimers of warranty, or limitations +of liability) contained within the Source Code Form of the Covered Software, +except that You may alter any license notices to the extent required to remedy +known factual inaccuracies. + + 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, indemnity +or liability obligations to one or more recipients of Covered Software. However, +You may do so only on Your own behalf, and not on behalf of any Contributor. +You must make it absolutely clear that any such warranty, support, indemnity, +or liability obligation is offered by You alone, and You hereby agree to indemnify +every Contributor for any liability incurred by such Contributor as a result +of warranty, support, indemnity or liability terms You offer. You may include +additional disclaimers of warranty and limitations of liability specific to +any jurisdiction. + + 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Software due to statute, judicial +order, or regulation then You must: (a) comply with the terms of this License +to the maximum extent possible; and (b) describe the limitations and the code +they affect. Such description must be placed in a text file included with +all distributions of the Covered Software under this License. Except to the +extent prohibited by statute or regulation, such description must be sufficiently +detailed for a recipient of ordinary skill to be able to understand it. + + 5. Termination + +5.1. The rights granted under this License will terminate automatically if +You fail to comply with any of its terms. However, if You become compliant, +then the rights granted under this License from a particular Contributor are +reinstated (a) provisionally, unless and until such Contributor explicitly +and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor +fails to notify You of the non-compliance by some reasonable means prior to +60 days after You have come back into compliance. Moreover, Your grants from +a particular Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the first +time You have received notice of non-compliance with this License from such +Contributor, and You become compliant prior to 30 days after Your receipt +of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent infringement +claim (excluding declaratory judgment actions, counter-claims, and cross-claims) +alleging that a Contributor Version directly or indirectly infringes any patent, +then the rights granted to You by any and all Contributors for the Covered +Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end +user license agreements (excluding distributors and resellers) which have +been validly granted by You or Your distributors under this License prior +to termination shall survive termination. + + 6. Disclaimer of Warranty + +Covered Software is provided under this License on an "as is" basis, without +warranty of any kind, either expressed, implied, or statutory, including, +without limitation, warranties that the Covered Software is free of defects, +merchantable, fit for a particular purpose or non-infringing. The entire risk +as to the quality and performance of the Covered Software is with You. Should +any Covered Software prove defective in any respect, You (not any Contributor) +assume the cost of any necessary servicing, repair, or correction. This disclaimer +of warranty constitutes an essential part of this License. No use of any Covered +Software is authorized under this License except under this disclaimer. + + 7. Limitation of Liability + +Under no circumstances and under no legal theory, whether tort (including +negligence), contract, or otherwise, shall any Contributor, or anyone who +distributes Covered Software as permitted above, be liable to You for any +direct, indirect, special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses, even if such party shall have been informed of the possibility +of such damages. This limitation of liability shall not apply to liability +for death or personal injury resulting from such party's negligence to the +extent applicable law prohibits such limitation. Some jurisdictions do not +allow the exclusion or limitation of incidental or consequential damages, +so this exclusion and limitation may not apply to You. + + 8. Litigation + +Any litigation relating to this License may be brought only in the courts +of a jurisdiction where the defendant maintains its principal place of business +and such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party's ability to bring cross-claims or counter-claims. + + 9. Miscellaneous + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it enforceable. +Any law or regulation which provides that the language of a contract shall +be construed against the drafter shall not be used to construe this License +against a Contributor. + + 10. Versions of the License + + 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section 10.3, +no one other than the license steward has the right to modify or publish new +versions of this License. Each version will be given a distinguishing version +number. + + 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version of +the License under which You originally received the Covered Software, or under +the terms of any subsequent version published by the license steward. + + 10.3. Modified Versions + +If you create software not governed by this License, and you want to create +a new license for such software, you may create and use a modified version +of this License if you rename the license and remove any references to the +name of the license steward (except to note that such modified license differs +from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With Secondary +Licenses under the terms of this version of the License, the notice described +in Exhibit B of this License must be attached. Exhibit A - Source Code Form +License Notice + +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + +This Source Code Form is "Incompatible With Secondary Licenses", as defined +by the Mozilla Public License, v. 2.0. diff --git a/LICENSES/MPL-2.0.txt b/LICENSES/MPL-2.0.txt new file mode 100644 index 0000000..09f2798 --- /dev/null +++ b/LICENSES/MPL-2.0.txt @@ -0,0 +1,312 @@ +Mozilla Public License Version 2.0 + + 1. Definitions + +1.1. "Contributor" means each individual or legal entity that creates, contributes +to the creation of, or owns Covered Software. + +1.2. "Contributor Version" means the combination of the Contributions of others +(if any) used by a Contributor and that particular Contributor's Contribution. + + 1.3. "Contribution" means Covered Software of a particular Contributor. + +1.4. "Covered Software" means Source Code Form to which the initial Contributor +has attached the notice in Exhibit A, the Executable Form of such Source Code +Form, and Modifications of such Source Code Form, in each case including portions +thereof. + + 1.5. "Incompatible With Secondary Licenses" means + +(a) that the initial Contributor has attached the notice described in Exhibit +B to the Covered Software; or + +(b) that the Covered Software was made available under the terms of version +1.1 or earlier of the License, but not also under the terms of a Secondary +License. + +1.6. "Executable Form" means any form of the work other than Source Code Form. + +1.7. "Larger Work" means a work that combines Covered Software with other +material, in a separate file or files, that is not Covered Software. + + 1.8. "License" means this document. + +1.9. "Licensable" means having the right to grant, to the maximum extent possible, +whether at the time of the initial grant or subsequently, any and all of the +rights conveyed by this License. + + 1.10. "Modifications" means any of the following: + +(a) any file in Source Code Form that results from an addition to, deletion +from, or modification of the contents of Covered Software; or + +(b) any new file in Source Code Form that contains any Covered Software. + +1.11. "Patent Claims" of a Contributor means any patent claim(s), including +without limitation, method, process, and apparatus claims, in any patent Licensable +by such Contributor that would be infringed, but for the grant of the License, +by the making, using, selling, offering for sale, having made, import, or +transfer of either its Contributions or its Contributor Version. + +1.12. "Secondary License" means either the GNU General Public License, Version +2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General +Public License, Version 3.0, or any later versions of those licenses. + +1.13. "Source Code Form" means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") means an individual or a legal entity exercising rights +under this License. For legal entities, "You" includes any entity that controls, +is controlled by, or is under common control with You. For purposes of this +definition, "control" means (a) the power, direct or indirect, to cause the +direction or management of such entity, whether by contract or otherwise, +or (b) ownership of more than fifty percent (50%) of the outstanding shares +or beneficial ownership of such entity. + + 2. License Grants and Conditions + + 2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive +license: + +(a) under intellectual property rights (other than patent or trademark) Licensable +by such Contributor to use, reproduce, make available, modify, display, perform, +distribute, and otherwise exploit its Contributions, either on an unmodified +basis, with Modifications, or as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer for +sale, have made, import, and otherwise transfer either its Contributions or +its Contributor Version. + + 2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution become +effective for each Contribution on the date the Contributor first distributes +such Contribution. + + 2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under this +License. No additional rights or licenses will be implied from the distribution +or licensing of Covered Software under this License. Notwithstanding Section +2.1(b) above, no patent license is granted by a Contributor: + +(a) for any code that a Contributor has removed from Covered Software; or + +(b) for infringements caused by: (i) Your and any other third party's modifications +of Covered Software, or (ii) the combination of its Contributions with other +software (except as part of its Contributor Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of its +Contributions. + +This License does not grant any rights in the trademarks, service marks, or +logos of any Contributor (except as may be necessary to comply with the notice +requirements in Section 3.4). + + 2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to distribute +the Covered Software under a subsequent version of this License (see Section +10.2) or under the terms of a Secondary License (if permitted under the terms +of Section 3.3). + + 2.5. Representation + +Each Contributor represents that the Contributor believes its Contributions +are its original creation(s) or it has sufficient rights to grant the rights +to its Contributions conveyed by this License. + + 2.6. Fair Use + +This License is not intended to limit any rights You have under applicable +copyright doctrines of fair use, fair dealing, or other equivalents. + + 2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in +Section 2.1. + + 3. Responsibilities + + 3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any Modifications +that You create or to which You contribute, must be under the terms of this +License. You must inform recipients that the Source Code Form of the Covered +Software is governed by the terms of this License, and how they can obtain +a copy of this License. You may not attempt to alter or restrict the recipients' +rights in the Source Code Form. + + 3.2. Distribution of Executable Form + + If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code Form, +as described in Section 3.1, and You must inform recipients of the Executable +Form how they can obtain a copy of such Source Code Form by reasonable means +in a timely manner, at a charge no more than the cost of distribution to the +recipient; and + +(b) You may distribute such Executable Form under the terms of this License, +or sublicense it under different terms, provided that the license for the +Executable Form does not attempt to limit or alter the recipients' rights +in the Source Code Form under this License. + + 3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, provided +that You also comply with the requirements of this License for the Covered +Software. If the Larger Work is a combination of Covered Software with a work +governed by one or more Secondary Licenses, and the Covered Software is not +Incompatible With Secondary Licenses, this License permits You to additionally +distribute such Covered Software under the terms of such Secondary License(s), +so that the recipient of the Larger Work may, at their option, further distribute +the Covered Software under the terms of either this License or such Secondary +License(s). + + 3.4. Notices + +You may not remove or alter the substance of any license notices (including +copyright notices, patent notices, disclaimers of warranty, or limitations +of liability) contained within the Source Code Form of the Covered Software, +except that You may alter any license notices to the extent required to remedy +known factual inaccuracies. + + 3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, indemnity +or liability obligations to one or more recipients of Covered Software. However, +You may do so only on Your own behalf, and not on behalf of any Contributor. +You must make it absolutely clear that any such warranty, support, indemnity, +or liability obligation is offered by You alone, and You hereby agree to indemnify +every Contributor for any liability incurred by such Contributor as a result +of warranty, support, indemnity or liability terms You offer. You may include +additional disclaimers of warranty and limitations of liability specific to +any jurisdiction. + + 4. Inability to Comply Due to Statute or Regulation + +If it is impossible for You to comply with any of the terms of this License +with respect to some or all of the Covered Software due to statute, judicial +order, or regulation then You must: (a) comply with the terms of this License +to the maximum extent possible; and (b) describe the limitations and the code +they affect. Such description must be placed in a text file included with +all distributions of the Covered Software under this License. Except to the +extent prohibited by statute or regulation, such description must be sufficiently +detailed for a recipient of ordinary skill to be able to understand it. + + 5. Termination + +5.1. The rights granted under this License will terminate automatically if +You fail to comply with any of its terms. However, if You become compliant, +then the rights granted under this License from a particular Contributor are +reinstated (a) provisionally, unless and until such Contributor explicitly +and finally terminates Your grants, and (b) on an ongoing basis, if such Contributor +fails to notify You of the non-compliance by some reasonable means prior to +60 days after You have come back into compliance. Moreover, Your grants from +a particular Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the first +time You have received notice of non-compliance with this License from such +Contributor, and You become compliant prior to 30 days after Your receipt +of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent infringement +claim (excluding declaratory judgment actions, counter-claims, and cross-claims) +alleging that a Contributor Version directly or indirectly infringes any patent, +then the rights granted to You by any and all Contributors for the Covered +Software under Section 2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all end +user license agreements (excluding distributors and resellers) which have +been validly granted by You or Your distributors under this License prior +to termination shall survive termination. + + 6. Disclaimer of Warranty + +Covered Software is provided under this License on an "as is" basis, without +warranty of any kind, either expressed, implied, or statutory, including, +without limitation, warranties that the Covered Software is free of defects, +merchantable, fit for a particular purpose or non-infringing. The entire risk +as to the quality and performance of the Covered Software is with You. Should +any Covered Software prove defective in any respect, You (not any Contributor) +assume the cost of any necessary servicing, repair, or correction. This disclaimer +of warranty constitutes an essential part of this License. No use of any Covered +Software is authorized under this License except under this disclaimer. + + 7. Limitation of Liability + +Under no circumstances and under no legal theory, whether tort (including +negligence), contract, or otherwise, shall any Contributor, or anyone who +distributes Covered Software as permitted above, be liable to You for any +direct, indirect, special, incidental, or consequential damages of any character +including, without limitation, damages for lost profits, loss of goodwill, +work stoppage, computer failure or malfunction, or any and all other commercial +damages or losses, even if such party shall have been informed of the possibility +of such damages. This limitation of liability shall not apply to liability +for death or personal injury resulting from such party's negligence to the +extent applicable law prohibits such limitation. Some jurisdictions do not +allow the exclusion or limitation of incidental or consequential damages, +so this exclusion and limitation may not apply to You. + + 8. Litigation + +Any litigation relating to this License may be brought only in the courts +of a jurisdiction where the defendant maintains its principal place of business +and such litigation shall be governed by laws of that jurisdiction, without +reference to its conflict-of-law provisions. Nothing in this Section shall +prevent a party's ability to bring cross-claims or counter-claims. + + 9. Miscellaneous + +This License represents the complete agreement concerning the subject matter +hereof. If any provision of this License is held to be unenforceable, such +provision shall be reformed only to the extent necessary to make it enforceable. +Any law or regulation which provides that the language of a contract shall +be construed against the drafter shall not be used to construe this License +against a Contributor. + + 10. Versions of the License + + 10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section 10.3, +no one other than the license steward has the right to modify or publish new +versions of this License. Each version will be given a distinguishing version +number. + + 10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version of +the License under which You originally received the Covered Software, or under +the terms of any subsequent version published by the license steward. + + 10.3. Modified Versions + +If you create software not governed by this License, and you want to create +a new license for such software, you may create and use a modified version +of this License if you rename the license and remove any references to the +name of the license steward (except to note that such modified license differs +from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses + +If You choose to distribute Source Code Form that is Incompatible With Secondary +Licenses under the terms of this version of the License, the notice described +in Exhibit B of this License must be attached. Exhibit A - Source Code Form +License Notice + +This Source Code Form is subject to the terms of the Mozilla Public License, +v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain +one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular file, +then You may include the notice in a location (such as a LICENSE file in a +relevant directory) where a recipient would be likely to look for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice + +This Source Code Form is "Incompatible With Secondary Licenses", as defined +by the Mozilla Public License, v. 2.0. diff --git a/README.md b/README.md index 4580a42..74b96be 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,26 @@ + + # deploy-rs -#### A candidate for [serokell/deploy](https://github.com/serokell/deploy) + +A Simple multi-profile Nix-flake deploy tool. **This is very early development software, you should expect to find issues** -## Usage examples +## Usage -Example Nix expressions/configurations are in the [examples folder](./examples), here are various ways to deploy - `nix run github:notgne2/deploy-rs your-flake#node.profile` - `nix run github:notgne2/deploy-rs your-flake#node` - `nix run github:notgne2/deploy-rs your-flake` +## API + +Example Nix expressions/configurations are in the [examples folder](./examples). + ## Idea `deploy-rs` is a simple Rust program that will take a Nix flake and use it to deploy any of your defined profiles to your nodes. This is _strongly_ based off of [serokell/deploy](https://github.com/serokell/deploy), with the intention of eventually replacing it. @@ -21,4 +31,11 @@ This type of design (as opposed to more traditional tools like NixOps or morph) - ~~Ordered profiles~~ - Automatic rollbacks if one profile on node failed to deploy (partially implemented) -- UI (?) \ No newline at end of file +- UI (?) + +## About Serokell + +deploy-rs is maintained and funded with ❤️ by [Serokell](https://serokell.io/). +The names and logo for Serokell are trademark of Serokell OÜ. + +We love open source software! See [our other projects](https://serokell.io/community?utm_source=github) or [hire us](https://serokell.io/hire-us?utm_source=github) to design, develop and grow your idea! \ No newline at end of file diff --git a/examples/simple/flake.lock.license b/examples/simple/flake.lock.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/examples/simple/flake.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/examples/simple/flake.nix b/examples/simple/flake.nix index 6e516d3..800363f 100644 --- a/examples/simple/flake.nix +++ b/examples/simple/flake.nix @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + { description = "Deploy GNU hello to localhost"; @@ -5,19 +9,17 @@ deploy.nodes.example = { hostname = "localhost"; profiles.hello = { - user = "test_deploy"; + user = "balsoft"; path = nixpkgs.legacyPackages.x86_64-linux.hello; # Just to test that it's working activate = "$PROFILE/bin/hello"; }; }; - checks = builtins.mapAttrs - (_: pkgs: { - jsonschema = pkgs.runCommandNoCC "jsonschema-deploy-simple" { } - "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${ + checks = builtins.mapAttrs (_: pkgs: { + jsonschema = pkgs.runCommandNoCC "jsonschema-deploy-simple" { } + "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${ pkgs.writeText "deploy.json" (builtins.toJSON self.deploy) } ${../../interface/deploy.json} && touch $out"; - }) - nixpkgs.legacyPackages; + }) nixpkgs.legacyPackages; }; } diff --git a/examples/system/README.md b/examples/system/README.md index 1dea41f..daf649a 100644 --- a/examples/system/README.md +++ b/examples/system/README.md @@ -1,3 +1,9 @@ + + # Example nixos system deployment This is an example of how to deploy a full nixos system with a separate user unit to a bare machine. diff --git a/examples/system/bare.nix b/examples/system/bare.nix index 282080f..46ba3b2 100644 --- a/examples/system/bare.nix +++ b/examples/system/bare.nix @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + { imports = [ ./common.nix ]; diff --git a/examples/system/common.nix b/examples/system/common.nix index 7e7448e..83ea225 100644 --- a/examples/system/common.nix +++ b/examples/system/common.nix @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + { boot.loader.systemd-boot.enable = true; diff --git a/examples/system/configuration.nix b/examples/system/configuration.nix index b2b55cf..6d4234a 100644 --- a/examples/system/configuration.nix +++ b/examples/system/configuration.nix @@ -1,7 +1,11 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + { imports = [ ./common.nix ]; - networking.hostName = "example-nixos-syyyystem"; + networking.hostName = "example-nixos-system"; users.users.hello = { isNormalUser = true; diff --git a/examples/system/flake.lock.license b/examples/system/flake.lock.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/examples/system/flake.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/examples/system/flake.nix b/examples/system/flake.nix index 383960b..5179258 100644 --- a/examples/system/flake.nix +++ b/examples/system/flake.nix @@ -1,7 +1,10 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + { description = "Deploy a full system with hello service as a separate profile"; - outputs = { self, nixpkgs }: { nixosConfigurations.example-nixos-system = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; @@ -10,7 +13,8 @@ nixosConfigurations.bare = nixpkgs.lib.nixosSystem { system = "x86_64-linux"; - modules = [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; + modules = + [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; }; # This is the application we actually want to run @@ -24,7 +28,8 @@ system = { sshUser = "admin"; activate = "$PROFILE/bin/switch-to-configuration switch"; - path = self.nixosConfigurations.example-nixos-system.config.system.build.toplevel; + path = + self.nixosConfigurations.example-nixos-system.config.system.build.toplevel; user = "root"; }; hello = { @@ -38,9 +43,9 @@ checks = builtins.mapAttrs (_: pkgs: { jsonschema = pkgs.runCommandNoCC "jsonschema-deploy-system" { } - "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${ - pkgs.writeText "deploy.json" (builtins.toJSON self.deploy) - } ${../../interface/deploy.json} && touch $out"; + "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${ + pkgs.writeText "deploy.json" (builtins.toJSON self.deploy) + } ${../../interface/deploy.json} && touch $out"; }) nixpkgs.legacyPackages; }; } diff --git a/examples/system/hello.nix b/examples/system/hello.nix index 8c207f1..df57308 100644 --- a/examples/system/hello.nix +++ b/examples/system/hello.nix @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + nixpkgs: let pkgs = nixpkgs.legacyPackages.x86_64-linux; @@ -14,11 +18,10 @@ let text = mkService "hello" { unitConfig.WantedBy = [ "multi-user.target" ]; path = [ pkgs.hello ]; - script = "hello -g lel; touch $HOME/oof"; + script = "hello"; }; }; -in -pkgs.writeShellScriptBin "activate" '' +in pkgs.writeShellScriptBin "activate" '' mkdir -p $HOME/.config/systemd/user rm $HOME/.config/systemd/user/hello.service ln -s ${service} $HOME/.config/systemd/user/hello.service diff --git a/examples/system/nix-pub.pem.license b/examples/system/nix-pub.pem.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/examples/system/nix-pub.pem.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/examples/system/nix.key.license b/examples/system/nix.key.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/examples/system/nix.key.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/flake.lock.license b/flake.lock.license new file mode 100644 index 0000000..9e9897d --- /dev/null +++ b/flake.lock.license @@ -0,0 +1,3 @@ +SPDX-FileCopyrightText: 2020 Serokell + +SPDX-License-Identifier: MPL-2.0 \ No newline at end of file diff --git a/flake.nix b/flake.nix index acc3f18..d3d1817 100644 --- a/flake.nix +++ b/flake.nix @@ -1,3 +1,7 @@ +# SPDX-FileCopyrightText: 2020 Serokell +# +# SPDX-License-Identifier: MPL-2.0 + { inputs = { naersk.url = "github:nmattia/naersk/master"; @@ -10,8 +14,7 @@ let pkgs = import nixpkgs { inherit system; }; naersk-lib = pkgs.callPackage naersk { }; - in - { + in { defaultPackage = naersk-lib.buildPackage ./.; defaultApp = { diff --git a/src/activate.rs b/src/activate.rs index 8e22aea..3c7c16d 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2020 Serokell +// +// SPDX-License-Identifier: MPL-2.0 + use clap::Clap; use std::process::Stdio; diff --git a/src/main.rs b/src/main.rs index 668b697..9e50674 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2020 Serokell +// +// SPDX-License-Identifier: MPL-2.0 + use clap::Clap; use std::process::Stdio; @@ -17,7 +21,7 @@ mod utils; /// Simple Rust rewrite of a simple Nix Flake deployment tool #[derive(Clap, Debug)] -#[clap(version = "1.0", author = "notgne2 ")] +#[clap(version = "1.0", author = "Serokell ")] struct Opts { /// The flake to deploy #[clap(default_value = ".")] diff --git a/src/utils/data.rs b/src/utils/data.rs index 0753508..d1dae5b 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2020 Serokell +// +// SPDX-License-Identifier: MPL-2.0 + use merge::Merge; use std::collections::HashMap; diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 42bd0b4..247d5e5 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2020 Serokell +// +// SPDX-License-Identifier: MPL-2.0 + use super::data; use tokio::process::Command; diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 8861692..5802627 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2020 Serokell +// +// SPDX-License-Identifier: MPL-2.0 + use std::borrow::Cow; use std::path::PathBuf; diff --git a/src/utils/push.rs b/src/utils/push.rs index 0e1b9ba..c87c32b 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -1,3 +1,7 @@ +// SPDX-FileCopyrightText: 2020 Serokell +// +// SPDX-License-Identifier: MPL-2.0 + use super::data; use std::process::Stdio; -- cgit v1.3.1 From 3bd43f92e6c59f65b6120886c4ee75b6a9391522 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 13 Oct 2020 18:27:27 -0700 Subject: Auto rollback if deployment is not confirmed --- Cargo.lock | 132 ++++++++++++++++++++++++++-- Cargo.toml | 5 +- src/activate.rs | 245 +++++++++++++++++++++++++++++++++++++--------------- src/utils/data.rs | 4 + src/utils/deploy.rs | 52 ++++++++++- 5 files changed, 354 insertions(+), 84 deletions(-) (limited to 'Cargo.toml') diff --git a/Cargo.lock b/Cargo.lock index ab3bdba..bfb1db4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -87,6 +87,9 @@ name = "deploy-rs" version = "0.1.0" dependencies = [ "clap", + "fork", + "futures-util", + "inotify", "log", "merge", "pretty_env_logger", @@ -116,6 +119,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "fork" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48ab1b42fd10696f85fa0bd0856da17ba05b18d527dbaf846359441ecb808d92" +dependencies = [ + "libc", +] + [[package]] name = "fuchsia-zircon" version = "0.3.3" @@ -134,9 +146,46 @@ checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" [[package]] name = "futures-core" -version = "0.3.5" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674eaa0056896d5ada519900dbf97ead2e46a7b6621e8160d79e2f2e1e2784b" + +[[package]] +name = "futures-macro" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" +checksum = "f57ed14da4603b2554682e9f2ff3c65d7567b53188db96cb71538217fc64581b" +dependencies = [ + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd26820a9f3637f1302da8bceba3ff33adbe53464b54ca24d4e2d4f1db30f94" +dependencies = [ + "once_cell", +] + +[[package]] +name = "futures-util" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a894a0acddba51a2d49a6f4263b1e64b8c579ece8af50fa86503d52cd1eea34" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project", + "pin-utils", + "proc-macro-hack", + "proc-macro-nested", + "slab", +] [[package]] name = "hashbrown" @@ -181,6 +230,29 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "inotify" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46dd0a94b393c730779ccfd2a872b67b1eb67be3fc33082e733bdb38b5fde4d4" +dependencies = [ + "bitflags", + "futures-core", + "inotify-sys", + "libc", + "mio", + "tokio", +] + +[[package]] +name = "inotify-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e74a1aa87c59aeff6ef2cc2fa62d41bc43f54952f55652656b18a02fd5e356c0" +dependencies = [ + "libc", +] + [[package]] name = "iovec" version = "0.1.4" @@ -214,9 +286,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.77" +version = "0.2.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" +checksum = "2448f6066e80e3bfc792e9c98bf705b4b0fc6e8ef5b43e5889aff0eaa9c58743" [[package]] name = "log" @@ -349,18 +421,50 @@ dependencies = [ "libc", ] +[[package]] +name = "once_cell" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "260e51e7efe62b592207e9e13a68e43692a7a279171d6ba57abd208bf23645ad" + [[package]] name = "os_str_bytes" version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2ac6fe3538f701e339953a3ebbe4f39941aababa8a3f6964635b24ab526daeac" +[[package]] +name = "pin-project" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffbc8e94b38ea3d2d8ba92aea2983b503cd75d0888d75b86bb37970b5698e15" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "0.4.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65ad2ae56b6abe3a1ee25f15ee605bacadb9a764edaba9c2bf4103800d4a1895" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "pin-project-lite" version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "pretty_env_logger" version = "0.4.0" @@ -395,11 +499,23 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99c605b9a0adc77b7211c6b1f722dcb613d68d66859a44f3d485a6da332b0598" + +[[package]] +name = "proc-macro-nested" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a" + [[package]] name = "proc-macro2" -version = "1.0.21" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] @@ -513,9 +629,9 @@ checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] name = "syn" -version = "1.0.41" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" +checksum = "e03e57e4fcbfe7749842d53e24ccb9aa12b7252dbe5e91d2acad31834c8b8fdd" dependencies = [ "proc-macro2", "quote", diff --git a/Cargo.toml b/Cargo.toml index 07b4144..e8474b4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,6 +20,9 @@ merge = "0.1.0" whoami = "0.9.0" log = "0.4" pretty_env_logger = "0.4" +inotify = "0.8" +futures-util = "0.3.6" +fork = "0.1" [[bin]] name = "deploy" @@ -27,4 +30,4 @@ path = "src/main.rs" [[bin]] name = "activate" -path = "src/activate.rs" +path = "src/activate.rs" \ No newline at end of file diff --git a/src/activate.rs b/src/activate.rs index 0b7d28d..ce6b286 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -4,11 +4,20 @@ use clap::Clap; +use futures_util::FutureExt; use std::process::Stdio; +use tokio::fs; use tokio::process::Command; +use tokio::time::timeout; + +use std::time::Duration; + +use futures_util::StreamExt; use std::path::Path; +use inotify::Inotify; + extern crate pretty_env_logger; #[macro_use] extern crate log; @@ -25,6 +34,8 @@ mod utils; struct Opts { profile_path: String, closure: String, + temp_path: String, + max_time: u16, /// Command for bootstrapping #[clap(long)] @@ -35,11 +46,162 @@ struct Opts { auto_rollback: bool, } +pub async fn deactivate(profile_path: &str) -> Result<(), Box> { + error!("De-activating due to error"); + + let nix_env_rollback_exit_status = Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--rollback") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await?; + + if !nix_env_rollback_exit_status.success() { + good_panic!("`nix-env --rollback` failed"); + } + + debug!("Listing generations"); + + let nix_env_list_generations_out = Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--list-generations") + .output() + .await?; + + if !nix_env_list_generations_out.status.success() { + good_panic!("Listing `nix-env` generations failed"); + } + + let generations_list = String::from_utf8(nix_env_list_generations_out.stdout)?; + + let last_generation_line = generations_list + .lines() + .last() + .expect("Expected to find a generation in list"); + + let last_generation_id = last_generation_line + .split_whitespace() + .next() + .expect("Expected to get ID from generation entry"); + + debug!("Removing generation entry {}", last_generation_line); + warn!("Removing generation by ID {}", last_generation_id); + + let nix_env_delete_generation_exit_status = Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--delete-generations") + .arg(last_generation_id) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await?; + + if !nix_env_delete_generation_exit_status.success() { + good_panic!("Failed to delete failed generation"); + } + + info!("Attempting re-activate last generation"); + + let re_activate_exit_status = Command::new(format!("{}/deploy-rs-activate", profile_path)) + .env("PROFILE", &profile_path) + .current_dir(&profile_path) + .status() + .await?; + + if !re_activate_exit_status.success() { + good_panic!("Failed to re-activate the last generation"); + } + + Ok(()) +} + +async fn deactivate_on_err(profile_path: &str, r: Result) -> A { + match r { + Ok(x) => x, + Err(err) => { + error!("Deactivating due to error: {:?}", err); + match deactivate(profile_path).await { + Ok(_) => (), + Err(err) => { + error!("Error de-activating, uh-oh: {:?}", err); + } + }; + + std::process::exit(1); + } + } +} + +pub async fn activation_confirmation( + profile_path: String, + temp_path: String, + max_time: u16, + closure: String, +) -> Result<(), Box> { + let lock_hash = &closure[11 /* /nix/store/ */ ..]; + let lock_path = format!("{}/activating-{}", temp_path, lock_hash); + + if let Some(parent) = Path::new(&lock_path).parent() { + fs::create_dir_all(parent).await?; + } + + fs::File::create(&lock_path).await?; + + let mut inotify = Inotify::init()?; + inotify.add_watch(lock_path, inotify::WatchMask::DELETE)?; + + match fork::daemon(false, false).map_err(|x| x.to_string())? { + fork::Fork::Child => { + std::thread::spawn(move || { + let mut rt = tokio::runtime::Runtime::new().unwrap(); + + rt.block_on(async move { + info!("Waiting for confirmation event..."); + + let mut buffer = [0; 32]; + let mut stream = + deactivate_on_err(&profile_path, inotify.event_stream(&mut buffer)).await; + + deactivate_on_err( + &profile_path, + deactivate_on_err( + &profile_path, + deactivate_on_err( + &profile_path, + timeout(Duration::from_secs(max_time as u64), stream.next()).await, + ) + .await + .ok_or("Watcher ended prematurely"), + ) + .await, + ) + .await; + }); + }) + .join() + .unwrap(); + + info!("Confirmation successful!"); + + std::process::exit(0); + } + fork::Fork::Parent(_) => { + std::process::exit(0); + } + } +} + pub async fn activate( profile_path: String, closure: String, bootstrap_cmd: Option, auto_rollback: bool, + temp_path: String, + max_time: u16, ) -> Result<(), Box> { info!("Activating profile"); @@ -83,80 +245,17 @@ pub async fn activate( match activate_status { Ok(s) if s.success() => (), - _ if auto_rollback => { - error!("Failed to execute activation command"); - - let nix_env_rollback_exit_status = Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--rollback") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await?; - - if !nix_env_rollback_exit_status.success() { - good_panic!("`nix-env --rollback` failed"); - } - - debug!("Listing generations"); - - let nix_env_list_generations_out = Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--list-generations") - .output() - .await?; - - if !nix_env_list_generations_out.status.success() { - good_panic!("Listing `nix-env` generations failed"); - } - - let generations_list = String::from_utf8(nix_env_list_generations_out.stdout)?; - - let last_generation_line = generations_list - .lines() - .last() - .expect("Expected to find a generation in list"); - - let last_generation_id = last_generation_line - .split_whitespace() - .next() - .expect("Expected to get ID from generation entry"); - - debug!("Removing generation entry {}", last_generation_line); - warn!("Removing generation by ID {}", last_generation_id); - - let nix_env_delete_generation_exit_status = Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--delete-generations") - .arg(last_generation_id) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await?; - - if !nix_env_delete_generation_exit_status.success() { - good_panic!("Failed to delete failed generation"); - } - - info!("Attempting re-activate last generation"); - - let re_activate_exit_status = Command::new(format!("{}/deploy-rs-activate", profile_path)) - .env("PROFILE", &profile_path) - .current_dir(&profile_path) - .status() - .await?; + _ if auto_rollback => return Ok(deactivate(&profile_path).await?), + _ => (), + } - if !re_activate_exit_status.success() { - good_panic!("Failed to re-activate the last generation"); - } + info!("Activation succeeded, now performing post-activation checks"); - std::process::exit(1); - } - _ => {} - } + deactivate_on_err( + &profile_path, + activation_confirmation(profile_path.clone(), temp_path, max_time, closure).await, + ) + .await; Ok(()) } @@ -176,6 +275,8 @@ async fn main() -> Result<(), Box> { opts.closure, opts.bootstrap_cmd, opts.auto_rollback, + opts.temp_path, + opts.max_time, ) .await?; diff --git a/src/utils/data.rs b/src/utils/data.rs index f72f9a7..351b9ae 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -44,6 +44,10 @@ 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 7301967..e3493ba 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: MPL-2.0 +use std::borrow::Cow; use tokio::process::Command; fn build_activate_command( @@ -11,9 +12,13 @@ fn build_activate_command( closure: &str, bootstrap_cmd: &Option, auto_rollback: bool, + temp_path: &Cow, + max_time: u16, ) -> String { - let mut self_activate_command = - format!("{} '{}' '{}'", activate_path_str, profile_path, closure); + let mut self_activate_command = format!( + "{} '{}' '{}' {} {}", + activate_path_str, profile_path, closure, temp_path, max_time + ); if let Some(sudo_cmd) = &sudo { self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); @@ -41,6 +46,8 @@ fn test_activation_command_builder() { let closure = "/blah/etc"; let bootstrap_cmd = None; let auto_rollback = true; + let temp_path = &"/tmp/deploy-rs".into(); + let max_time = 30; assert_eq!( build_activate_command( @@ -50,8 +57,10 @@ fn test_activation_command_builder() { closure, &bootstrap_cmd, auto_rollback, + temp_path, + max_time ), - "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' --auto-rollback" + "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' /tmp/deploy-rs 30 --auto-rollback" .to_string(), ); } @@ -67,6 +76,13 @@ 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 { + Some(x) => x.into(), + None => "/tmp/deploy-rs".into(), + }; + + let max_time = deploy_data.profile.profile_settings.max_time.unwrap_or(30); + let self_activate_command = build_activate_command( activate_path_str, &deploy_defs.sudo, @@ -74,6 +90,8 @@ pub async fn deploy_profile( &deploy_data.profile.profile_settings.path, &deploy_data.profile.profile_settings.bootstrap, deploy_data.merged_settings.auto_rollback, + &temp_path, + max_time, ); let hostname = match deploy_data.cmd_overrides.hostname { @@ -94,5 +112,33 @@ pub async fn deploy_profile( good_panic!("Activation over SSH failed"); } + info!("Success, attempting to connect to the node to confirm deployment"); + + let mut c = Command::new("ssh"); + let mut ssh_confirm_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); + + for ssh_opt in &deploy_data.merged_settings.ssh_opts { + ssh_confirm_command = ssh_confirm_command.arg(ssh_opt); + } + + let lock_hash = &deploy_data.profile.profile_settings.path[11 /* /nix/store/ */ ..]; + let lock_path = format!("{}/activating-{}", temp_path, lock_hash); + + let mut confirm_command = format!("rm {}", lock_path); + if let Some(sudo_cmd) = &deploy_defs.sudo { + confirm_command = format!("{} {}", sudo_cmd, confirm_command); + } + + let ssh_exit_status = ssh_confirm_command.arg(confirm_command).status().await?; + + if !ssh_exit_status.success() { + good_panic!( + "Failed to confirm deployment, the node will roll back in <{} seconds", + max_time + ); + } + + info!("Deployment confirmed."); + Ok(()) } -- cgit v1.3.1