From 76dbef54af3bbfbf81edd8a45f6ba8dea0db47f9 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 10:48:21 -0700 Subject: stuff --- .gitignore | 2 + Cargo.lock | 674 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 18 ++ flake.lock | 77 +++++++ flake.nix | 22 ++ src/main.rs | 598 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 6 files changed, 1391 insertions(+) create mode 100644 .gitignore create mode 100644 Cargo.lock create mode 100644 Cargo.toml create mode 100644 flake.lock create mode 100644 flake.nix create mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d787b70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/target +/result diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..ab3bdba --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,674 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "aho-corasick" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" +dependencies = [ + "memchr", +] + +[[package]] +name = "arc-swap" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bytes" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + +[[package]] +name = "clap" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "indexmap", + "lazy_static", + "os_str_bytes", + "strsim", + "termcolor", + "textwrap", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap_derive" +version = "3.0.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "deploy-rs" +version = "0.1.0" +dependencies = [ + "clap", + "log", + "merge", + "pretty_env_logger", + "serde", + "serde_derive", + "serde_json", + "tokio", + "whoami", +] + +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +dependencies = [ + "bitflags", + "fuchsia-zircon-sys", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" + +[[package]] +name = "futures-core" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399" + +[[package]] +name = "hashbrown" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00d63df3d41950fb462ed38308eea019113ad1508da725bbedcd0fa5a85ef5f7" + +[[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "hermit-abi" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c30f6d0bc6b00693347368a67d41b58f2fb851215ff1da49e90fe2c5c667151" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + +[[package]] +name = "indexmap" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "iovec" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" +dependencies = [ + "libc", +] + +[[package]] +name = "itoa" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6" + +[[package]] +name = "kernel32-sys" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2f96b10ec2560088a8e76961b00d47107b3a625fecb76dedb29ee7ccbf98235" + +[[package]] +name = "log" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "memchr" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" + +[[package]] +name = "merge" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10bbef93abb1da61525bbc45eeaff6473a41907d19f8f9aa5168d214e10693e9" +dependencies = [ + "merge_derive", + "num-traits", +] + +[[package]] +name = "merge_derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209d075476da2e63b4b29e72a2ef627b840589588e71400a25e3565c4f849d07" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "mio" +version = "0.6.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" +dependencies = [ + "cfg-if", + "fuchsia-zircon", + "fuchsia-zircon-sys", + "iovec", + "kernel32-sys", + "libc", + "log", + "miow 0.2.1", + "net2", + "slab", + "winapi 0.2.8", +] + +[[package]] +name = "mio-named-pipes" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" +dependencies = [ + "log", + "mio", + "miow 0.3.5", + "winapi 0.3.9", +] + +[[package]] +name = "mio-uds" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" +dependencies = [ + "iovec", + "libc", + "mio", +] + +[[package]] +name = "miow" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" +dependencies = [ + "kernel32-sys", + "net2", + "winapi 0.2.8", + "ws2_32-sys", +] + +[[package]] +name = "miow" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07b88fb9795d4d36d62a012dfbf49a8f5cf12751f36d31a9dbe66d528e58979e" +dependencies = [ + "socket2", + "winapi 0.3.9", +] + +[[package]] +name = "net2" +version = "0.2.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ebc3ec692ed7c9a255596c67808dee269f64655d8baf7b4f0638e51ba1d6853" +dependencies = [ + "cfg-if", + "libc", + "winapi 0.3.9", +] + +[[package]] +name = "num-traits" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[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-lite" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715" + +[[package]] +name = "pretty_env_logger" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36e28516df94f3dd551a587da5357459d9b36d945a7c37c3557928c1c2ff2a2c" +dependencies = [ + "unicode-xid", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "redox_syscall" +version = "0.1.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce" + +[[package]] +name = "regex" +version = "1.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", + "thread_local", +] + +[[package]] +name = "regex-syntax" +version = "0.6.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" + +[[package]] +name = "ryu" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" + +[[package]] +name = "serde" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96fe57af81d28386a513cbc6858332abc6117cfdb5999647c6444b8f43a370a5" + +[[package]] +name = "serde_derive" +version = "1.0.116" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f630a6370fd8e457873b4bd2ffdae75408bc291ba72be773772a4c2a065d9ae8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.57" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "164eacbdb13512ec2745fb09d51fd5b22b0d65ed294a1dcf7285a360c80a675c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signal-hook-registry" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e12110bc539e657a646068aaf5eb5b63af9d0c1f7b29c97113fad80e15f035" +dependencies = [ + "arc-swap", + "libc", +] + +[[package]] +name = "slab" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" + +[[package]] +name = "socket2" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1fa70dc5c8104ec096f4fe7ede7a221d35ae13dcd19ba1ad9a81d2cab9a1c44" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi 0.3.9", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.41" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6690e3e9f692504b941dc6c3b188fd28df054f7fb8469ab40680df52fdcc842b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "termcolor" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "thread_local" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "tokio" +version = "0.2.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d34ca54d84bf2b5b4d7d31e901a8464f7b60ac145a284fba25ceb801f2ddccd" +dependencies = [ + "bytes", + "fnv", + "futures-core", + "iovec", + "lazy_static", + "libc", + "memchr", + "mio", + "mio-named-pipes", + "mio-uds", + "num_cpus", + "pin-project-lite", + "signal-hook-registry", + "slab", + "tokio-macros", + "winapi 0.3.9", +] + +[[package]] +name = "tokio-macros" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0c3acc6aa564495a0f2e1d59fab677cd7f81a19994cfc7f3ad0e64301560389" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-segmentation" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" + +[[package]] +name = "unicode-width" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3" + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" + +[[package]] +name = "whoami" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7884773ab69074615cb8f8425d0e53f11710786158704fca70f53e71b0e05504" + +[[package]] +name = "winapi" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-build" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi 0.3.9", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "ws2_32-sys" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" +dependencies = [ + "winapi 0.2.8", + "winapi-build", +] 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" diff --git a/flake.lock b/flake.lock new file mode 100644 index 0000000..0a41d47 --- /dev/null +++ b/flake.lock @@ -0,0 +1,77 @@ +{ + "nodes": { + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1597138680, + "narHash": "sha256-3pDN/W17wjVDbrkgo60xQSb24+QAPQ7ulsUq5atNni0=", + "owner": "nmattia", + "repo": "naersk", + "rev": "529e910a3f423a8211f8739290014b754b2555b6", + "type": "github" + }, + "original": { + "owner": "nmattia", + "ref": "master", + "repo": "naersk", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1601091160, + "narHash": "sha256-26UI9LGjRO8Sv253zJZkoapP260QkJPQ2+vRyC1i+kI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2768436826543af2b1540e4fe6b5afa15850e155", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1600387253, + "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "72b9660dc18ba347f7cd41a9504fc181a6d87dc3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + } + }, + "utils": { + "locked": { + "lastModified": 1600209923, + "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 0000000..2efb943 --- /dev/null +++ b/flake.nix @@ -0,0 +1,22 @@ +{ + inputs = { + naersk.url = "github:nmattia/naersk/master"; + nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + utils.url = "github:numtide/flake-utils"; + }; + + outputs = { self, nixpkgs, utils, naersk }: + utils.lib.eachDefaultSystem (system: + let + pkgs = import nixpkgs { inherit system; }; + naersk-lib = pkgs.callPackage naersk { }; + in + { + defaultPackage = naersk-lib.buildPackage ./.; + + defaultApp = { + type = "app"; + program = "${self.defaultPackage."${system}"}/bin/deploy-rs"; + }; + }); +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..2184392 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,598 @@ +use clap::Clap; +use merge::Merge; +use std::collections::HashMap; + +use std::borrow::Cow; + +use std::process::Stdio; +use tokio::process::Command; + +use std::path::Path; + +use std::process; + +extern crate pretty_env_logger; +#[macro_use] +extern crate log; + +#[macro_use] +extern crate serde_derive; + +macro_rules! good_panic { + ($($tts:tt)*) => {{ + error!($($tts)*); + process::exit(1); + }} +} + +/// Simple Rust rewrite of a simple Nix Flake deployment tool +#[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, + /// Prepare server (for first deployments) + #[clap(short, long)] + prime: 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), +} + +#[derive(Deserialize, Debug, Clone, Merge)] +pub struct GenericSettings { + #[serde(rename(deserialize = "sshUser"))] + pub ssh_user: Option, + pub user: Option, + #[serde( + skip_serializing_if = "Vec::is_empty", + default, + rename(deserialize = "sshOpts") + )] + #[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, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct NodeSettings { + pub hostname: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct ProfileSettings { + pub path: String, + pub activate: Option, + pub bootstrap: Option, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Profile { + #[serde(flatten)] + pub profile_settings: ProfileSettings, + #[serde(flatten)] + pub generic_settings: GenericSettings, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Node { + #[serde(flatten)] + pub generic_settings: GenericSettings, + #[serde(flatten)] + pub node_settings: NodeSettings, + + pub profiles: HashMap, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Data { + #[serde(flatten)] + pub generic_settings: GenericSettings, + pub nodes: HashMap, +} + +async fn deploy_profile( + profile: &Profile, + profile_name: &str, + node: &Node, + node_name: &str, + top_settings: &GenericSettings, + supports_flakes: bool, + repo: &str, +) -> Result<(), Box> { + info!( + "Deploying profile `{}` for node `{}`", + profile_name, node_name + ); + + let mut merged_settings = top_settings.clone(); + merged_settings.merge(node.generic_settings.clone()); + merged_settings.merge(profile.generic_settings.clone()); + + let ssh_user: Cow = match &merged_settings.ssh_user { + Some(u) => u.into(), + None => whoami::username().into(), + }; + + let profile_user: Cow = match &merged_settings.user { + Some(x) => x.into(), + None => match &merged_settings.ssh_user { + Some(x) => x.into(), + None => good_panic!( + "Neither user nor sshUser set for profile `{}` of node `{}`", + profile_name, + node_name + ), + }, + }; + + let sudo: Option = match merged_settings.user { + Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user).into()), + _ => None, + }; + + let profile_path = match &profile_user[..] { + "root" => format!("/nix/var/nix/profiles/{}", profile_name), + _ => format!( + "/nix/var/nix/profiles/per-user/{}/{}", + profile_user, profile_name + ), + }; + + info!( + "Building profile `{}` for node `{}`", + profile_name, node_name + ); + if supports_flakes { + Command::new("nix") + .arg("build") + .arg("--no-link") + .arg(format!( + "{}#deploy.nodes.{}.profiles.{}.path", + repo, node_name, profile_name + )) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + } else { + Command::new("nix-build") + .arg(&repo) + .arg("-A") + .arg(format!( + "deploy.nodes.{}.profiles.{}.path", + node_name, profile_name + )) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + } + + let current_exe = std::env::current_exe().expect("Expected to find current executable path"); + + if !current_exe.starts_with("/nix/store/") { + good_panic!("The deploy binary must be in the Nix store"); + } + + if let Ok(local_key) = std::env::var("LOCAL_KEY") { + info!( + "Signing key present! Signing profile `{}` for node `{}`", + profile_name, node_name + ); + + Command::new("nix") + .arg("sign-paths") + .arg("-r") + .arg("-k") + .arg(local_key) + .arg(&profile.profile_settings.path) + .arg(¤t_exe) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + } + + info!("Copying profile `{} for node `{}`", profile_name, node_name); + + let mut copy_command_ = Command::new("nix"); + let mut copy_command = copy_command_.arg("copy"); + + if let Some(true) = merged_settings.fast_connection { + copy_command = copy_command.arg("--substitute-on-destination"); + } + + let ssh_opts_str = merged_settings + .ssh_opts + // This should provide some extra safety, but it also breaks for some reason, oh well + // .iter() + // .map(|x| format!("'{}'", x)) + // .collect::>() + .join(" "); + + copy_command + .arg("--no-check-sigs") + .arg("--to") + .arg(format!( + "ssh://{}@{}", + ssh_user, node.node_settings.hostname + )) + .arg(&profile.profile_settings.path) + .arg(¤t_exe) + .env("NIX_SSHOPTS", ssh_opts_str) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + info!( + "Activating profile `{}` for node `{}`", + profile_name, node_name + ); + + let mut self_activate_command = format!( + "{} activate '{}' '{}'", + current_exe.as_path().to_str().unwrap(), + profile_path, + profile.profile_settings.path, + ); + + if let Some(sudo_cmd) = sudo { + self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); + } + + if let Some(ref bootstrap_cmd) = profile.profile_settings.bootstrap { + self_activate_command = format!( + "{} --bootstrap-cmd '{}'", + self_activate_command, bootstrap_cmd + ); + } + + if let Some(ref activate_cmd) = profile.profile_settings.activate { + self_activate_command = format!( + "{} --activate-cmd '{}'", + self_activate_command, activate_cmd + ); + } + + let mut c = Command::new("ssh"); + let mut ssh_command = c.arg(format!( + "ssh://{}@{}", + ssh_user, node.node_settings.hostname + )); + + for ssh_opt in merged_settings.ssh_opts { + ssh_command = ssh_command.arg(ssh_opt); + } + + ssh_command.arg(self_activate_command).spawn()?.await?; + + Ok(()) +} + +#[inline] +async fn deploy_all_profiles( + node: &Node, + node_name: &str, + supports_flakes: bool, + repo: &str, + top_settings: &GenericSettings, + prime: bool, +) -> Result<(), Box> { + info!("Deploying all profiles for `{}`", node_name); + + if prime { + info!("Bootstrapping {}", node_name); + + let profile = match node.profiles.get("system") { + Some(x) => x, + None => good_panic!("No system profile was found, needed for priming"), + }; + + deploy_profile( + &profile, + "system", + node, + node_name, + top_settings, + supports_flakes, + repo, + ) + .await?; + } + + for (profile_name, profile) in &node.profiles { + // This will have already been deployed + if prime && profile_name == "system" { + continue; + } + + deploy_profile( + &profile, + profile_name, + node, + node_name, + top_settings, + supports_flakes, + repo, + ) + .await?; + } + + Ok(()) +} + +#[tokio::main] + +async fn main() -> Result<(), Box> { + if let Err(_) = std::env::var("DEPLOY_LOG") { + std::env::set_var("DEPLOY_LOG", "info"); + } + + pretty_env_logger::init_custom_env("DEPLOY_LOG"); + + let opts: Opts = Opts::parse(); + + match opts.subcmd { + SubCommand::Deploy(deploy_opts) => { + let flake_fragment_start = deploy_opts.flake.find('#'); + + let (repo, maybe_fragment) = match flake_fragment_start { + Some(s) => (&deploy_opts.flake[..s], Some(&deploy_opts.flake[s + 1..])), + None => (deploy_opts.flake.as_str(), None), + }; + + let (maybe_node, maybe_profile) = match maybe_fragment { + Some(fragment) => { + let fragment_profile_start = fragment.find('.'); + match fragment_profile_start { + Some(s) => (Some(&fragment[..s]), Some(&fragment[s + 1..])), + None => (Some(fragment), None), + } + } + None => (None, None), + }; + + let test_flake_status = Command::new("nix") + .arg("eval") + .arg("--expr") + .arg("builtins.getFlake") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await?; + + let supports_flakes = test_flake_status.success(); + + let data_json = match supports_flakes { + true => { + let c = Command::new("nix") + .arg("eval") + .arg("--json") + .arg(format!("{}#deploy", repo)) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + // TODO forward input args? + .output() + .await?; + + String::from_utf8(c.stdout)? + } + false => { + let c = Command::new("nix-instanciate") + .arg("--strict") + .arg("--read-write-mode") + .arg("--json") + .arg("--eval") + .arg("--E") + .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .output() + .await?; + + String::from_utf8(c.stdout)? + } + }; + + let data: Data = serde_json::from_str(&data_json)?; + + match (maybe_node, maybe_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), + }; + + deploy_profile( + &profile, + profile_name, + node, + node_name, + &data.generic_settings, + supports_flakes, + repo, + ) + .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), + }; + + deploy_all_profiles( + node, + node_name, + supports_flakes, + repo, + &data.generic_settings, + deploy_opts.prime, + ) + .await?; + } + (None, None) => { + info!("Deploying all profiles on all nodes"); + + for (node_name, node) in &data.nodes { + deploy_all_profiles( + node, + node_name, + supports_flakes, + repo, + &data.generic_settings, + deploy_opts.prime, + ) + .await?; + } + } + (None, Some(_)) => good_panic!( + "Profile provided without a node, this is not (currently) supported" + ), + }; + } + SubCommand::Activate(activate_opts) => { + info!("Activating profile"); + + Command::new("nix-env") + .arg("-p") + .arg(&activate_opts.profile_path) + .arg("--set") + .arg(&activate_opts.closure) + .stdout(Stdio::null()) + .spawn()? + .await?; + + if let (Some(bootstrap_cmd), false) = ( + activate_opts.bootstrap_cmd, + !Path::new(&activate_opts.profile_path).exists(), + ) { + let bootstrap_status = Command::new("bash") + .arg("-c") + .arg(&bootstrap_cmd) + .env("PROFILE", &activate_opts.profile_path) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await; + + match bootstrap_status { + Ok(s) if s.success() => (), + _ => { + tokio::fs::remove_file(&activate_opts.profile_path).await?; + good_panic!("Failed to execute bootstrap command"); + } + } + } + + if let Some(activate_cmd) = activate_opts.activate_cmd { + let activate_status = Command::new("bash") + .arg("-c") + .arg(&activate_cmd) + .env("PROFILE", &activate_opts.profile_path) + .status() + .await; + + match activate_status { + Ok(s) if s.success() => (), + _ if activate_opts.auto_rollback => { + Command::new("nix-env") + .arg("-p") + .arg(&activate_opts.profile_path) + .arg("--rollback") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + let c = Command::new("nix-env") + .arg("-p") + .arg(&activate_opts.profile_path) + .arg("--list-generations") + .output() + .await?; + let generations_list = String::from_utf8(c.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); + + Command::new("nix-env") + .arg("-p") + .arg(&activate_opts.profile_path) + .arg("--delete-generations") + .arg(last_generation_id) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + // TODO: why are we doing this? + // to run the older version as long as the command is the same? + Command::new("bash") + .arg("-c") + .arg(&activate_cmd) + .spawn()? + .await?; + + good_panic!("Failed to execute activation command"); + } + _ => {} + } + } + } + } + + Ok(()) +} -- cgit v1.2.3 From 1b9cb58802cd295bd91b822812f195c02367e350 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 12:17:36 -0700 Subject: add check sigs flag --- src/main.rs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 2184392..0599b11 100644 --- a/src/main.rs +++ b/src/main.rs @@ -46,6 +46,9 @@ struct DeployOpts { /// Prepare server (for first deployments) #[clap(short, long)] prime: bool, + /// Check signatures when using `nix copy` + #[clap(short, long)] + checksigs: bool, } /// Activate a profile on your current machine @@ -135,6 +138,7 @@ async fn deploy_profile( node_name: &str, top_settings: &GenericSettings, supports_flakes: bool, + check_sigs: bool, repo: &str, ) -> Result<(), Box> { info!( @@ -240,6 +244,10 @@ async fn deploy_profile( copy_command = copy_command.arg("--substitute-on-destination"); } + if !check_sigs { + copy_command = copy_command.arg("--no-check-sigs"); + } + let ssh_opts_str = merged_settings .ssh_opts // This should provide some extra safety, but it also breaks for some reason, oh well @@ -249,7 +257,6 @@ async fn deploy_profile( .join(" "); copy_command - .arg("--no-check-sigs") .arg("--to") .arg(format!( "ssh://{}@{}", @@ -316,6 +323,7 @@ async fn deploy_all_profiles( repo: &str, top_settings: &GenericSettings, prime: bool, + check_sigs: bool, ) -> Result<(), Box> { info!("Deploying all profiles for `{}`", node_name); @@ -334,6 +342,7 @@ async fn deploy_all_profiles( node_name, top_settings, supports_flakes, + check_sigs, repo, ) .await?; @@ -352,6 +361,7 @@ async fn deploy_all_profiles( node_name, top_settings, supports_flakes, + check_sigs, repo, ) .await?; @@ -374,7 +384,6 @@ async fn main() -> Result<(), Box> { match opts.subcmd { SubCommand::Deploy(deploy_opts) => { let flake_fragment_start = deploy_opts.flake.find('#'); - let (repo, maybe_fragment) = match flake_fragment_start { Some(s) => (&deploy_opts.flake[..s], Some(&deploy_opts.flake[s + 1..])), None => (deploy_opts.flake.as_str(), None), @@ -453,6 +462,7 @@ async fn main() -> Result<(), Box> { node_name, &data.generic_settings, supports_flakes, + deploy_opts.checksigs, repo, ) .await?; @@ -470,6 +480,7 @@ async fn main() -> Result<(), Box> { repo, &data.generic_settings, deploy_opts.prime, + deploy_opts.checksigs, ) .await?; } @@ -484,6 +495,7 @@ async fn main() -> Result<(), Box> { repo, &data.generic_settings, deploy_opts.prime, + deploy_opts.checksigs, ) .await?; } -- cgit v1.2.3 From 00f75951211bacc5bab4317e56376f03c74dabb7 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 12:25:30 -0700 Subject: Add (untested) profiles order support --- src/main.rs | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 0599b11..9d55efc 100644 --- a/src/main.rs +++ b/src/main.rs @@ -122,6 +122,12 @@ pub struct Node { 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)] @@ -348,7 +354,21 @@ async fn deploy_all_profiles( .await?; } - for (profile_name, profile) in &node.profiles { + let mut profiles_list: Vec<&str> = node.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 { + 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) { + Some(x) => x, + None => good_panic!("No profile was found named `{}`", profile_name), + }; + // This will have already been deployed if prime && profile_name == "system" { continue; -- cgit v1.2.3 From a71eaa3cec59e8084f890bf89efbffc5f765fd9e Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 13:37:57 -0700 Subject: Mildly modularize and seperate deploy and push (untested) --- src/main.rs | 164 +++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 118 insertions(+), 46 deletions(-) diff --git a/src/main.rs b/src/main.rs index 9d55efc..a3c526f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,6 @@ use clap::Clap; use merge::Merge; -use std::collections::HashMap; +use std::{collections::HashMap, path::PathBuf}; use std::borrow::Cow; @@ -137,25 +137,19 @@ pub struct Data { pub nodes: HashMap, } -async fn deploy_profile( - profile: &Profile, +struct DeployData<'a> { + pub sudo: Option, + pub ssh_user: Cow<'a, str>, + pub profile_user: Cow<'a, str>, + pub profile_path: String, + pub current_exe: PathBuf, +} + +async fn make_deploy_data<'a>( profile_name: &str, - node: &Node, node_name: &str, - top_settings: &GenericSettings, - supports_flakes: bool, - check_sigs: bool, - repo: &str, -) -> Result<(), Box> { - info!( - "Deploying profile `{}` for node `{}`", - profile_name, node_name - ); - - let mut merged_settings = top_settings.clone(); - merged_settings.merge(node.generic_settings.clone()); - merged_settings.merge(profile.generic_settings.clone()); - + merged_settings: &'a GenericSettings, +) -> Result, Box> { let ssh_user: Cow = match &merged_settings.ssh_user { Some(u) => u.into(), None => whoami::username().into(), @@ -173,11 +167,6 @@ async fn deploy_profile( }, }; - let sudo: Option = match merged_settings.user { - Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user).into()), - _ => None, - }; - let profile_path = match &profile_user[..] { "root" => format!("/nix/var/nix/profiles/{}", profile_name), _ => format!( @@ -186,6 +175,42 @@ async fn deploy_profile( ), }; + let sudo: Option = match merged_settings.user { + Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user).into()), + _ => None, + }; + + let current_exe = std::env::current_exe().expect("Expected to find current executable path"); + + if !current_exe.starts_with("/nix/store/") { + good_panic!("The deploy binary must be in the Nix store"); + } + + Ok(DeployData { + sudo, + ssh_user, + profile_user, + profile_path, + current_exe, + }) +} + +async fn push_profile( + profile: &Profile, + profile_name: &str, + node: &Node, + node_name: &str, + supports_flakes: bool, + check_sigs: bool, + repo: &str, + merged_settings: &GenericSettings, + deploy_data: &DeployData<'_>, +) -> Result<(), Box> { + info!( + "Deploying profile `{}` for node `{}`", + profile_name, node_name + ); + info!( "Building profile `{}` for node `{}`", profile_name, node_name @@ -216,12 +241,6 @@ async fn deploy_profile( .await?; } - let current_exe = std::env::current_exe().expect("Expected to find current executable path"); - - if !current_exe.starts_with("/nix/store/") { - good_panic!("The deploy binary must be in the Nix store"); - } - if let Ok(local_key) = std::env::var("LOCAL_KEY") { info!( "Signing key present! Signing profile `{}` for node `{}`", @@ -234,7 +253,7 @@ async fn deploy_profile( .arg("-k") .arg(local_key) .arg(&profile.profile_settings.path) - .arg(¤t_exe) + .arg(&deploy_data.current_exe) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()? @@ -266,16 +285,27 @@ async fn deploy_profile( .arg("--to") .arg(format!( "ssh://{}@{}", - ssh_user, node.node_settings.hostname + deploy_data.ssh_user, node.node_settings.hostname )) .arg(&profile.profile_settings.path) - .arg(¤t_exe) + .arg(&deploy_data.current_exe) .env("NIX_SSHOPTS", ssh_opts_str) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()? .await?; + Ok(()) +} + +async fn deploy_profile( + profile: &Profile, + profile_name: &str, + node: &Node, + node_name: &str, + merged_settings: &GenericSettings, + deploy_data: &DeployData<'_>, +) -> Result<(), Box> { info!( "Activating profile `{}` for node `{}`", profile_name, node_name @@ -283,12 +313,12 @@ async fn deploy_profile( let mut self_activate_command = format!( "{} activate '{}' '{}'", - current_exe.as_path().to_str().unwrap(), - profile_path, + deploy_data.current_exe.as_path().to_str().unwrap(), + deploy_data.profile_path, profile.profile_settings.path, ); - if let Some(sudo_cmd) = sudo { + if let Some(sudo_cmd) = &deploy_data.sudo { self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); } @@ -309,10 +339,10 @@ async fn deploy_profile( let mut c = Command::new("ssh"); let mut ssh_command = c.arg(format!( "ssh://{}@{}", - ssh_user, node.node_settings.hostname + deploy_data.ssh_user, node.node_settings.hostname )); - for ssh_opt in merged_settings.ssh_opts { + for ssh_opt in &merged_settings.ssh_opts { ssh_command = ssh_command.arg(ssh_opt); } @@ -321,6 +351,48 @@ async fn deploy_profile( Ok(()) } +async fn deploy_profile_todo( + top_settings: &GenericSettings, + profile: &Profile, + profile_name: &str, + node: &Node, + node_name: &str, + supports_flakes: bool, + check_sigs: bool, + repo: &str, +) -> Result<(), Box> { + let mut merged_settings = top_settings.clone(); + merged_settings.merge(node.generic_settings.clone()); + merged_settings.merge(profile.generic_settings.clone()); + + let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; + + push_profile( + profile, + profile_name, + node, + node_name, + supports_flakes, + check_sigs, + repo, + &merged_settings, + &deploy_data, + ) + .await?; + + deploy_profile( + profile, + profile_name, + node, + node_name, + &merged_settings, + &deploy_data, + ) + .await?; + + Ok(()) +} + #[inline] async fn deploy_all_profiles( node: &Node, @@ -341,12 +413,12 @@ async fn deploy_all_profiles( None => good_panic!("No system profile was found, needed for priming"), }; - deploy_profile( - &profile, + deploy_profile_todo( + top_settings, + profile, "system", node, node_name, - top_settings, supports_flakes, check_sigs, repo, @@ -374,12 +446,12 @@ async fn deploy_all_profiles( continue; } - deploy_profile( - &profile, + deploy_profile_todo( + top_settings, + profile, profile_name, node, node_name, - top_settings, supports_flakes, check_sigs, repo, @@ -475,12 +547,12 @@ async fn main() -> Result<(), Box> { None => good_panic!("No profile was found named `{}`", profile_name), }; - deploy_profile( - &profile, + deploy_profile_todo( + &data.generic_settings, + profile, profile_name, node, node_name, - &data.generic_settings, supports_flakes, deploy_opts.checksigs, repo, -- cgit v1.2.3 From ff347d4204369d40b6b04eedf8e2421bcd7018ab Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 13:55:46 -0700 Subject: Crudely perform pushes before deploys, with little data re-use (untested) --- src/main.rs | 135 +++++++++++++++++++++++++++++++----------------------------- 1 file changed, 69 insertions(+), 66 deletions(-) diff --git a/src/main.rs b/src/main.rs index a3c526f..59b79a5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -351,81 +351,63 @@ async fn deploy_profile( Ok(()) } -async fn deploy_profile_todo( - top_settings: &GenericSettings, - profile: &Profile, - profile_name: &str, - node: &Node, - node_name: &str, - supports_flakes: bool, - check_sigs: bool, - repo: &str, -) -> Result<(), Box> { - let mut merged_settings = top_settings.clone(); - merged_settings.merge(node.generic_settings.clone()); - merged_settings.merge(profile.generic_settings.clone()); - - let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; - - push_profile( - profile, - profile_name, - node, - node_name, - supports_flakes, - check_sigs, - repo, - &merged_settings, - &deploy_data, - ) - .await?; - - deploy_profile( - profile, - profile_name, - node, - node_name, - &merged_settings, - &deploy_data, - ) - .await?; - - Ok(()) -} - #[inline] -async fn deploy_all_profiles( +async fn push_all_profiles( node: &Node, node_name: &str, supports_flakes: bool, repo: &str, top_settings: &GenericSettings, - prime: bool, check_sigs: bool, ) -> Result<(), Box> { info!("Deploying all profiles for `{}`", node_name); - if prime { - info!("Bootstrapping {}", node_name); + let mut profiles_list: Vec<&str> = node.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 { + if !profiles_list.contains(&profile_name.as_str()) { + profiles_list.push(&profile_name); + } + } - let profile = match node.profiles.get("system") { + for profile_name in profiles_list { + let profile = match node.profiles.get(profile_name) { Some(x) => x, - None => good_panic!("No system profile was found, needed for priming"), + None => good_panic!("No profile was found named `{}`", profile_name), }; - deploy_profile_todo( - top_settings, + let mut merged_settings = top_settings.clone(); + merged_settings.merge(node.generic_settings.clone()); + merged_settings.merge(profile.generic_settings.clone()); + + let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; + + push_profile( profile, - "system", + profile_name, node, node_name, supports_flakes, check_sigs, repo, + &merged_settings, + &deploy_data, ) .await?; } + Ok(()) +} + +#[inline] +async fn deploy_all_profiles( + node: &Node, + node_name: &str, + top_settings: &GenericSettings, +) -> 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(); // Add any profiles which weren't in the provided order list @@ -441,20 +423,19 @@ async fn deploy_all_profiles( None => good_panic!("No profile was found named `{}`", profile_name), }; - // This will have already been deployed - if prime && profile_name == "system" { - continue; - } + let mut merged_settings = top_settings.clone(); + merged_settings.merge(node.generic_settings.clone()); + merged_settings.merge(profile.generic_settings.clone()); + + let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; - deploy_profile_todo( - top_settings, + deploy_profile( profile, profile_name, node, node_name, - supports_flakes, - check_sigs, - repo, + &merged_settings, + &deploy_data, ) .await?; } @@ -547,8 +528,14 @@ async fn main() -> Result<(), Box> { None => good_panic!("No profile was found named `{}`", profile_name), }; - deploy_profile_todo( - &data.generic_settings, + 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 = + make_deploy_data(profile_name, node_name, &merged_settings).await?; + + push_profile( profile, profile_name, node, @@ -556,6 +543,18 @@ async fn main() -> Result<(), Box> { supports_flakes, deploy_opts.checksigs, repo, + &merged_settings, + &deploy_data, + ) + .await?; + + deploy_profile( + profile, + profile_name, + node, + node_name, + &merged_settings, + &deploy_data, ) .await?; } @@ -565,32 +564,36 @@ async fn main() -> Result<(), Box> { None => good_panic!("No node was found named `{}`", node_name), }; - deploy_all_profiles( + push_all_profiles( node, node_name, supports_flakes, repo, &data.generic_settings, - deploy_opts.prime, 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 { - deploy_all_profiles( + push_all_profiles( node, node_name, supports_flakes, repo, &data.generic_settings, - deploy_opts.prime, 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" -- cgit v1.2.3 From 622ae7c1b46b188171121486ef93d582a4180495 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 13:59:49 -0700 Subject: fix some logging --- src/main.rs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main.rs b/src/main.rs index 59b79a5..cd57692 100644 --- a/src/main.rs +++ b/src/main.rs @@ -43,9 +43,6 @@ struct DeployOpts { /// The flake to deploy #[clap(default_value = ".")] flake: String, - /// Prepare server (for first deployments) - #[clap(short, long)] - prime: bool, /// Check signatures when using `nix copy` #[clap(short, long)] checksigs: bool, @@ -207,14 +204,15 @@ async fn push_profile( deploy_data: &DeployData<'_>, ) -> Result<(), Box> { info!( - "Deploying profile `{}` for node `{}`", + "Pushing profile `{}` for node `{}`", profile_name, node_name ); - info!( - "Building profile `{}` for node `{}`", + debug!( + "Building profile `{} for node `{}`", profile_name, node_name ); + if supports_flakes { Command::new("nix") .arg("build") @@ -260,7 +258,7 @@ async fn push_profile( .await?; } - info!("Copying profile `{} for node `{}`", profile_name, node_name); + debug!("Copying profile `{} for node `{}`", profile_name, node_name); let mut copy_command_ = Command::new("nix"); let mut copy_command = copy_command_.arg("copy"); @@ -360,7 +358,7 @@ async fn push_all_profiles( top_settings: &GenericSettings, check_sigs: bool, ) -> Result<(), Box> { - info!("Deploying all profiles for `{}`", node_name); + info!("Pushing all profiles for `{}`", node_name); let mut profiles_list: Vec<&str> = node.profiles_order.iter().map(|x| x.as_ref()).collect(); -- cgit v1.2.3 From 294a40a4ac3bb098f5a6d77f1323c1e5eca260d6 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 14:30:54 -0700 Subject: some basic modularization --- src/main.rs | 320 ++++-------------------------------------------------------- 1 file changed, 20 insertions(+), 300 deletions(-) diff --git a/src/main.rs b/src/main.rs index cd57692..7e96941 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,11 @@ use clap::Clap; -use merge::Merge; -use std::{collections::HashMap, path::PathBuf}; - -use std::borrow::Cow; use std::process::Stdio; use tokio::process::Command; -use std::path::Path; +use merge::Merge; -use std::process; +use std::path::Path; extern crate pretty_env_logger; #[macro_use] @@ -18,12 +14,10 @@ extern crate log; #[macro_use] extern crate serde_derive; -macro_rules! good_panic { - ($($tts:tt)*) => {{ - error!($($tts)*); - process::exit(1); - }} -} +#[macro_use] +mod utils; + +// use utils::*; /// Simple Rust rewrite of a simple Nix Flake deployment tool #[derive(Clap, Debug)] @@ -73,289 +67,13 @@ enum SubCommand { Activate(ActivateOpts), } -#[derive(Deserialize, Debug, Clone, Merge)] -pub struct GenericSettings { - #[serde(rename(deserialize = "sshUser"))] - pub ssh_user: Option, - pub user: Option, - #[serde( - skip_serializing_if = "Vec::is_empty", - default, - rename(deserialize = "sshOpts") - )] - #[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, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct NodeSettings { - pub hostname: String, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct ProfileSettings { - pub path: String, - pub activate: Option, - pub bootstrap: Option, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Profile { - #[serde(flatten)] - pub profile_settings: ProfileSettings, - #[serde(flatten)] - pub generic_settings: GenericSettings, -} - -#[derive(Deserialize, Debug, Clone)] -pub struct Node { - #[serde(flatten)] - 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)] -pub struct Data { - #[serde(flatten)] - pub generic_settings: GenericSettings, - pub nodes: HashMap, -} - -struct DeployData<'a> { - pub sudo: Option, - pub ssh_user: Cow<'a, str>, - pub profile_user: Cow<'a, str>, - pub profile_path: String, - pub current_exe: PathBuf, -} - -async fn make_deploy_data<'a>( - profile_name: &str, - node_name: &str, - merged_settings: &'a GenericSettings, -) -> Result, Box> { - let ssh_user: Cow = match &merged_settings.ssh_user { - Some(u) => u.into(), - None => whoami::username().into(), - }; - - let profile_user: Cow = match &merged_settings.user { - Some(x) => x.into(), - None => match &merged_settings.ssh_user { - Some(x) => x.into(), - None => good_panic!( - "Neither user nor sshUser set for profile `{}` of node `{}`", - profile_name, - node_name - ), - }, - }; - - let profile_path = match &profile_user[..] { - "root" => format!("/nix/var/nix/profiles/{}", profile_name), - _ => format!( - "/nix/var/nix/profiles/per-user/{}/{}", - profile_user, profile_name - ), - }; - - let sudo: Option = match merged_settings.user { - Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user).into()), - _ => None, - }; - - let current_exe = std::env::current_exe().expect("Expected to find current executable path"); - - if !current_exe.starts_with("/nix/store/") { - good_panic!("The deploy binary must be in the Nix store"); - } - - Ok(DeployData { - sudo, - ssh_user, - profile_user, - profile_path, - current_exe, - }) -} - -async fn push_profile( - profile: &Profile, - profile_name: &str, - node: &Node, - node_name: &str, - supports_flakes: bool, - check_sigs: bool, - repo: &str, - merged_settings: &GenericSettings, - deploy_data: &DeployData<'_>, -) -> Result<(), Box> { - info!( - "Pushing profile `{}` for node `{}`", - profile_name, node_name - ); - - debug!( - "Building profile `{} for node `{}`", - profile_name, node_name - ); - - if supports_flakes { - Command::new("nix") - .arg("build") - .arg("--no-link") - .arg(format!( - "{}#deploy.nodes.{}.profiles.{}.path", - repo, node_name, profile_name - )) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - } else { - Command::new("nix-build") - .arg(&repo) - .arg("-A") - .arg(format!( - "deploy.nodes.{}.profiles.{}.path", - node_name, profile_name - )) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - } - - if let Ok(local_key) = std::env::var("LOCAL_KEY") { - info!( - "Signing key present! Signing profile `{}` for node `{}`", - profile_name, node_name - ); - - Command::new("nix") - .arg("sign-paths") - .arg("-r") - .arg("-k") - .arg(local_key) - .arg(&profile.profile_settings.path) - .arg(&deploy_data.current_exe) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - } - - debug!("Copying profile `{} for node `{}`", profile_name, node_name); - - let mut copy_command_ = Command::new("nix"); - let mut copy_command = copy_command_.arg("copy"); - - if let Some(true) = merged_settings.fast_connection { - copy_command = copy_command.arg("--substitute-on-destination"); - } - - if !check_sigs { - copy_command = copy_command.arg("--no-check-sigs"); - } - - let ssh_opts_str = merged_settings - .ssh_opts - // This should provide some extra safety, but it also breaks for some reason, oh well - // .iter() - // .map(|x| format!("'{}'", x)) - // .collect::>() - .join(" "); - - copy_command - .arg("--to") - .arg(format!( - "ssh://{}@{}", - deploy_data.ssh_user, node.node_settings.hostname - )) - .arg(&profile.profile_settings.path) - .arg(&deploy_data.current_exe) - .env("NIX_SSHOPTS", ssh_opts_str) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - Ok(()) -} - -async fn deploy_profile( - profile: &Profile, - profile_name: &str, - node: &Node, - node_name: &str, - merged_settings: &GenericSettings, - deploy_data: &DeployData<'_>, -) -> Result<(), Box> { - info!( - "Activating profile `{}` for node `{}`", - profile_name, node_name - ); - - let mut self_activate_command = format!( - "{} activate '{}' '{}'", - deploy_data.current_exe.as_path().to_str().unwrap(), - deploy_data.profile_path, - profile.profile_settings.path, - ); - - if let Some(sudo_cmd) = &deploy_data.sudo { - self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); - } - - if let Some(ref bootstrap_cmd) = profile.profile_settings.bootstrap { - self_activate_command = format!( - "{} --bootstrap-cmd '{}'", - self_activate_command, bootstrap_cmd - ); - } - - if let Some(ref activate_cmd) = profile.profile_settings.activate { - self_activate_command = format!( - "{} --activate-cmd '{}'", - self_activate_command, activate_cmd - ); - } - - let mut c = Command::new("ssh"); - let mut ssh_command = c.arg(format!( - "ssh://{}@{}", - deploy_data.ssh_user, node.node_settings.hostname - )); - - for ssh_opt in &merged_settings.ssh_opts { - ssh_command = ssh_command.arg(ssh_opt); - } - - ssh_command.arg(self_activate_command).spawn()?.await?; - - Ok(()) -} - #[inline] async fn push_all_profiles( - node: &Node, + node: &utils::data::Node, node_name: &str, supports_flakes: bool, repo: &str, - top_settings: &GenericSettings, + top_settings: &utils::data::GenericSettings, check_sigs: bool, ) -> Result<(), Box> { info!("Pushing all profiles for `{}`", node_name); @@ -379,9 +97,10 @@ async fn push_all_profiles( merged_settings.merge(node.generic_settings.clone()); merged_settings.merge(profile.generic_settings.clone()); - let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; + let deploy_data = + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - push_profile( + utils::push::push_profile( profile, profile_name, node, @@ -400,9 +119,9 @@ async fn push_all_profiles( #[inline] async fn deploy_all_profiles( - node: &Node, + node: &utils::data::Node, node_name: &str, - top_settings: &GenericSettings, + top_settings: &utils::data::GenericSettings, ) -> Result<(), Box> { info!("Deploying all profiles for `{}`", node_name); @@ -425,9 +144,10 @@ async fn deploy_all_profiles( merged_settings.merge(node.generic_settings.clone()); merged_settings.merge(profile.generic_settings.clone()); - let deploy_data = make_deploy_data(profile_name, node_name, &merged_settings).await?; + let deploy_data = + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - deploy_profile( + utils::deploy::deploy_profile( profile, profile_name, node, @@ -513,7 +233,7 @@ async fn main() -> Result<(), Box> { } }; - let data: Data = serde_json::from_str(&data_json)?; + let data: utils::data::Data = serde_json::from_str(&data_json)?; match (maybe_node, maybe_profile) { (Some(node_name), Some(profile_name)) => { @@ -531,9 +251,9 @@ async fn main() -> Result<(), Box> { merged_settings.merge(profile.generic_settings.clone()); let deploy_data = - make_deploy_data(profile_name, node_name, &merged_settings).await?; + utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; - push_profile( + utils::push::push_profile( profile, profile_name, node, @@ -546,7 +266,7 @@ async fn main() -> Result<(), Box> { ) .await?; - deploy_profile( + utils::deploy::deploy_profile( profile, profile_name, node, -- cgit v1.2.3 From 73b99043a71f27f98bf11510fb8db46fa086383c Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 14:36:48 -0700 Subject: minor patches --- src/main.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/main.rs b/src/main.rs index 7e96941..74f7475 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,8 +17,6 @@ extern crate serde_derive; #[macro_use] mod utils; -// use utils::*; - /// Simple Rust rewrite of a simple Nix Flake deployment tool #[derive(Clap, Debug)] #[clap(version = "1.0", author = "notgne2 ")] @@ -81,7 +79,7 @@ async fn push_all_profiles( let mut profiles_list: Vec<&str> = node.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 { + for profile_name in node.profiles.keys() { if !profiles_list.contains(&profile_name.as_str()) { profiles_list.push(&profile_name); } @@ -128,7 +126,7 @@ async fn deploy_all_profiles( let mut profiles_list: Vec<&str> = node.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 { + for profile_name in node.profiles.keys() { if !profiles_list.contains(&profile_name.as_str()) { profiles_list.push(&profile_name); } @@ -164,7 +162,7 @@ async fn deploy_all_profiles( #[tokio::main] async fn main() -> Result<(), Box> { - if let Err(_) = std::env::var("DEPLOY_LOG") { + if std::env::var("DEPLOY_LOG").is_err() { std::env::set_var("DEPLOY_LOG", "info"); } @@ -403,8 +401,7 @@ async fn main() -> Result<(), Box> { .spawn()? .await?; - // TODO: why are we doing this? - // to run the older version as long as the command is the same? + // TODO: Find some way to make sure this command never changes, otherwise this will not work Command::new("bash") .arg("-c") .arg(&activate_cmd) -- cgit v1.2.3 From f73e393a75fcad939a240ff3b72cbc75813e90e3 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 14:37:43 -0700 Subject: Add missing files --- src/utils/data.rs | 64 +++++++++++++++++++++++++++++++ src/utils/deploy.rs | 57 +++++++++++++++++++++++++++ src/utils/mod.rs | 71 ++++++++++++++++++++++++++++++++++ src/utils/push.rs | 108 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 300 insertions(+) create mode 100644 src/utils/data.rs create mode 100644 src/utils/deploy.rs create mode 100644 src/utils/mod.rs create mode 100644 src/utils/push.rs diff --git a/src/utils/data.rs b/src/utils/data.rs new file mode 100644 index 0000000..779d913 --- /dev/null +++ b/src/utils/data.rs @@ -0,0 +1,64 @@ +use merge::Merge; + +use std::{collections::HashMap}; + +#[derive(Deserialize, Debug, Clone, Merge)] +pub struct GenericSettings { + #[serde(rename(deserialize = "sshUser"))] + pub ssh_user: Option, + pub user: Option, + #[serde( + skip_serializing_if = "Vec::is_empty", + default, + rename(deserialize = "sshOpts") + )] + #[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, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct NodeSettings { + pub hostname: String, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct ProfileSettings { + pub path: String, + pub activate: Option, + pub bootstrap: Option, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Profile { + #[serde(flatten)] + pub profile_settings: ProfileSettings, + #[serde(flatten)] + pub generic_settings: GenericSettings, +} + +#[derive(Deserialize, Debug, Clone)] +pub struct Node { + #[serde(flatten)] + 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)] +pub struct Data { + #[serde(flatten)] + pub generic_settings: GenericSettings, + pub nodes: HashMap, +} diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs new file mode 100644 index 0000000..7260e55 --- /dev/null +++ b/src/utils/deploy.rs @@ -0,0 +1,57 @@ +use super::data; + + +use tokio::process::Command; + +pub async fn deploy_profile( + profile: &data::Profile, + profile_name: &str, + node: &data::Node, + node_name: &str, + merged_settings: &data::GenericSettings, + deploy_data: &super::DeployData<'_>, +) -> Result<(), Box> { + info!( + "Activating profile `{}` for node `{}`", + profile_name, node_name + ); + + let mut self_activate_command = format!( + "{} activate '{}' '{}'", + deploy_data.current_exe.as_path().to_str().unwrap(), + deploy_data.profile_path, + profile.profile_settings.path, + ); + + if let Some(sudo_cmd) = &deploy_data.sudo { + self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); + } + + if let Some(ref bootstrap_cmd) = profile.profile_settings.bootstrap { + self_activate_command = format!( + "{} --bootstrap-cmd '{}'", + self_activate_command, bootstrap_cmd + ); + } + + if let Some(ref activate_cmd) = profile.profile_settings.activate { + self_activate_command = format!( + "{} --activate-cmd '{}'", + self_activate_command, activate_cmd + ); + } + + let mut c = Command::new("ssh"); + let mut ssh_command = c.arg(format!( + "ssh://{}@{}", + deploy_data.ssh_user, node.node_settings.hostname + )); + + for ssh_opt in &merged_settings.ssh_opts { + ssh_command = ssh_command.arg(ssh_opt); + } + + ssh_command.arg(self_activate_command).spawn()?.await?; + + Ok(()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs new file mode 100644 index 0000000..764e2e9 --- /dev/null +++ b/src/utils/mod.rs @@ -0,0 +1,71 @@ +use std::borrow::Cow; +use std::path::PathBuf; + +pub mod data; +pub mod deploy; +pub mod push; + +macro_rules! good_panic { + ($($tts:tt)*) => {{ + error!($($tts)*); + std::process::exit(1); + }} +} + +pub struct DeployData<'a> { + pub sudo: Option, + pub ssh_user: Cow<'a, str>, + pub profile_user: Cow<'a, str>, + pub profile_path: String, + pub current_exe: PathBuf, +} + +pub async fn make_deploy_data<'a>( + profile_name: &str, + node_name: &str, + merged_settings: &'a data::GenericSettings, +) -> Result, Box> { + let ssh_user: Cow = match &merged_settings.ssh_user { + Some(u) => u.into(), + None => whoami::username().into(), + }; + + let profile_user: Cow = match &merged_settings.user { + Some(x) => x.into(), + None => match &merged_settings.ssh_user { + Some(x) => x.into(), + None => good_panic!( + "Neither user nor sshUser set for profile `{}` of node `{}`", + profile_name, + node_name + ), + }, + }; + + let profile_path = match &profile_user[..] { + "root" => format!("/nix/var/nix/profiles/{}", profile_name), + _ => format!( + "/nix/var/nix/profiles/per-user/{}/{}", + profile_user, profile_name + ), + }; + + let sudo: Option = match merged_settings.user { + Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), + _ => None, + }; + + let current_exe = std::env::current_exe().expect("Expected to find current executable path"); + + if !current_exe.starts_with("/nix/store/") { + good_panic!("The deploy binary must be in the Nix store"); + } + + Ok(DeployData { + sudo, + ssh_user, + profile_user, + profile_path, + current_exe, + }) +} diff --git a/src/utils/push.rs b/src/utils/push.rs new file mode 100644 index 0000000..54ae013 --- /dev/null +++ b/src/utils/push.rs @@ -0,0 +1,108 @@ +use super::data; + +use std::process::Stdio; +use tokio::process::Command; + +pub async fn push_profile( + profile: &data::Profile, + profile_name: &str, + node: &data::Node, + node_name: &str, + supports_flakes: bool, + check_sigs: bool, + repo: &str, + merged_settings: &data::GenericSettings, + deploy_data: &super::DeployData<'_>, +) -> Result<(), Box> { + info!( + "Pushing profile `{}` for node `{}`", + profile_name, node_name + ); + + debug!( + "Building profile `{} for node `{}`", + profile_name, node_name + ); + + if supports_flakes { + Command::new("nix") + .arg("build") + .arg("--no-link") + .arg(format!( + "{}#deploy.nodes.{}.profiles.{}.path", + repo, node_name, profile_name + )) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + } else { + Command::new("nix-build") + .arg(&repo) + .arg("-A") + .arg(format!( + "deploy.nodes.{}.profiles.{}.path", + node_name, profile_name + )) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + } + + if let Ok(local_key) = std::env::var("LOCAL_KEY") { + info!( + "Signing key present! Signing profile `{}` for node `{}`", + profile_name, node_name + ); + + Command::new("nix") + .arg("sign-paths") + .arg("-r") + .arg("-k") + .arg(local_key) + .arg(&profile.profile_settings.path) + .arg(&deploy_data.current_exe) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + } + + debug!("Copying profile `{} for node `{}`", profile_name, node_name); + + let mut copy_command_ = Command::new("nix"); + let mut copy_command = copy_command_.arg("copy"); + + if let Some(true) = merged_settings.fast_connection { + copy_command = copy_command.arg("--substitute-on-destination"); + } + + if !check_sigs { + copy_command = copy_command.arg("--no-check-sigs"); + } + + let ssh_opts_str = merged_settings + .ssh_opts + // This should provide some extra safety, but it also breaks for some reason, oh well + // .iter() + // .map(|x| format!("'{}'", x)) + // .collect::>() + .join(" "); + + copy_command + .arg("--to") + .arg(format!( + "ssh://{}@{}", + deploy_data.ssh_user, node.node_settings.hostname + )) + .arg(&profile.profile_settings.path) + .arg(&deploy_data.current_exe) + .env("NIX_SSHOPTS", ssh_opts_str) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + Ok(()) +} -- cgit v1.2.3 From 916631d6319aa3125ededd548b174eb188c43e83 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 14:47:05 -0700 Subject: separate and add tests for flake parsing --- src/main.rs | 91 ++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 69 insertions(+), 22 deletions(-) diff --git a/src/main.rs b/src/main.rs index 74f7475..7818652 100644 --- a/src/main.rs +++ b/src/main.rs @@ -159,6 +159,68 @@ async fn deploy_all_profiles( Ok(()) } +#[derive(PartialEq, Debug)] +struct DeployFlake<'a> { + repo: &'a str, + node: Option<&'a str>, + profile: Option<&'a str>, +} + +fn parse_flake(flake: &str) -> DeployFlake { + let flake_fragment_start = flake.find('#'); + let (repo, maybe_fragment) = match flake_fragment_start { + Some(s) => (&flake[..s], Some(&flake[s + 1..])), + None => (flake, None), + }; + + let (node, profile) = match maybe_fragment { + Some(fragment) => { + let fragment_profile_start = fragment.find('.'); + match fragment_profile_start { + Some(s) => (Some(&fragment[..s]), Some(&fragment[s + 1..])), + None => (Some(fragment), None), + } + } + None => (None, None), + }; + + DeployFlake { + repo, + node, + profile, + } +} + +#[test] +fn test_parse_flake() { + assert_eq!( + parse_flake("../deploy/examples/system#example"), + DeployFlake { + repo: "../deploy/examples/system", + node: Some("example"), + profile: None + } + ); + + assert_eq!( + parse_flake("../deploy/examples/system#example.system"), + DeployFlake { + repo: "../deploy/examples/system", + node: Some("example"), + profile: Some("system") + } + ); + + assert_eq!( + parse_flake("../deploy/examples/system"), + DeployFlake { + repo: "../deploy/examples/system", + node: None, + profile: None, + } + ); +} + #[tokio::main] async fn main() -> Result<(), Box> { @@ -172,22 +234,7 @@ async fn main() -> Result<(), Box> { match opts.subcmd { SubCommand::Deploy(deploy_opts) => { - let flake_fragment_start = deploy_opts.flake.find('#'); - let (repo, maybe_fragment) = match flake_fragment_start { - Some(s) => (&deploy_opts.flake[..s], Some(&deploy_opts.flake[s + 1..])), - None => (deploy_opts.flake.as_str(), None), - }; - - let (maybe_node, maybe_profile) = match maybe_fragment { - Some(fragment) => { - let fragment_profile_start = fragment.find('.'); - match fragment_profile_start { - Some(s) => (Some(&fragment[..s]), Some(&fragment[s + 1..])), - None => (Some(fragment), None), - } - } - None => (None, None), - }; + let deploy_flake = parse_flake(deploy_opts.flake.as_str()); let test_flake_status = Command::new("nix") .arg("eval") @@ -205,7 +252,7 @@ async fn main() -> Result<(), Box> { let c = Command::new("nix") .arg("eval") .arg("--json") - .arg(format!("{}#deploy", repo)) + .arg(format!("{}#deploy", deploy_flake.repo)) .stdout(Stdio::null()) .stderr(Stdio::null()) // TODO forward input args? @@ -221,7 +268,7 @@ async fn main() -> Result<(), Box> { .arg("--json") .arg("--eval") .arg("--E") - .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) + .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", deploy_flake.repo)) .stdout(Stdio::null()) .stderr(Stdio::null()) .output() @@ -233,7 +280,7 @@ async fn main() -> Result<(), Box> { let data: utils::data::Data = serde_json::from_str(&data_json)?; - match (maybe_node, maybe_profile) { + match (deploy_flake.node, deploy_flake.profile) { (Some(node_name), Some(profile_name)) => { let node = match data.nodes.get(node_name) { Some(x) => x, @@ -258,7 +305,7 @@ async fn main() -> Result<(), Box> { node_name, supports_flakes, deploy_opts.checksigs, - repo, + deploy_flake.repo, &merged_settings, &deploy_data, ) @@ -284,7 +331,7 @@ async fn main() -> Result<(), Box> { node, node_name, supports_flakes, - repo, + deploy_flake.repo, &data.generic_settings, deploy_opts.checksigs, ) @@ -300,7 +347,7 @@ async fn main() -> Result<(), Box> { node, node_name, supports_flakes, - repo, + deploy_flake.repo, &data.generic_settings, deploy_opts.checksigs, ) -- cgit v1.2.3 From 889fb0d3f9eee9085883fe1f31e05b07be0939ec Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 15:00:16 -0700 Subject: separate out activation logic --- src/main.rs | 169 +++----------------------------------------------- src/utils/activate.rs | 108 ++++++++++++++++++++++++++++++++ src/utils/mod.rs | 72 +++++++++++++++++++-- 3 files changed, 184 insertions(+), 165 deletions(-) create mode 100644 src/utils/activate.rs diff --git a/src/main.rs b/src/main.rs index 7818652..cde4fc3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,8 +5,6 @@ use tokio::process::Command; use merge::Merge; -use std::path::Path; - extern crate pretty_env_logger; #[macro_use] extern crate log; @@ -158,69 +156,6 @@ async fn deploy_all_profiles( Ok(()) } - -#[derive(PartialEq, Debug)] -struct DeployFlake<'a> { - repo: &'a str, - node: Option<&'a str>, - profile: Option<&'a str>, -} - -fn parse_flake(flake: &str) -> DeployFlake { - let flake_fragment_start = flake.find('#'); - let (repo, maybe_fragment) = match flake_fragment_start { - Some(s) => (&flake[..s], Some(&flake[s + 1..])), - None => (flake, None), - }; - - let (node, profile) = match maybe_fragment { - Some(fragment) => { - let fragment_profile_start = fragment.find('.'); - match fragment_profile_start { - Some(s) => (Some(&fragment[..s]), Some(&fragment[s + 1..])), - None => (Some(fragment), None), - } - } - None => (None, None), - }; - - DeployFlake { - repo, - node, - profile, - } -} - -#[test] -fn test_parse_flake() { - assert_eq!( - parse_flake("../deploy/examples/system#example"), - DeployFlake { - repo: "../deploy/examples/system", - node: Some("example"), - profile: None - } - ); - - assert_eq!( - parse_flake("../deploy/examples/system#example.system"), - DeployFlake { - repo: "../deploy/examples/system", - node: Some("example"), - profile: Some("system") - } - ); - - assert_eq!( - parse_flake("../deploy/examples/system"), - DeployFlake { - repo: "../deploy/examples/system", - node: None, - profile: None, - } - ); -} - #[tokio::main] async fn main() -> Result<(), Box> { @@ -234,7 +169,7 @@ async fn main() -> Result<(), Box> { match opts.subcmd { SubCommand::Deploy(deploy_opts) => { - let deploy_flake = parse_flake(deploy_opts.flake.as_str()); + let deploy_flake = utils::parse_flake(deploy_opts.flake.as_str()); let test_flake_status = Command::new("nix") .arg("eval") @@ -364,102 +299,14 @@ async fn main() -> Result<(), Box> { }; } SubCommand::Activate(activate_opts) => { - info!("Activating profile"); - - Command::new("nix-env") - .arg("-p") - .arg(&activate_opts.profile_path) - .arg("--set") - .arg(&activate_opts.closure) - .stdout(Stdio::null()) - .spawn()? - .await?; - - if let (Some(bootstrap_cmd), false) = ( + utils::activate::activate( + activate_opts.profile_path, + activate_opts.closure, + activate_opts.activate_cmd, activate_opts.bootstrap_cmd, - !Path::new(&activate_opts.profile_path).exists(), - ) { - let bootstrap_status = Command::new("bash") - .arg("-c") - .arg(&bootstrap_cmd) - .env("PROFILE", &activate_opts.profile_path) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await; - - match bootstrap_status { - Ok(s) if s.success() => (), - _ => { - tokio::fs::remove_file(&activate_opts.profile_path).await?; - good_panic!("Failed to execute bootstrap command"); - } - } - } - - if let Some(activate_cmd) = activate_opts.activate_cmd { - let activate_status = Command::new("bash") - .arg("-c") - .arg(&activate_cmd) - .env("PROFILE", &activate_opts.profile_path) - .status() - .await; - - match activate_status { - Ok(s) if s.success() => (), - _ if activate_opts.auto_rollback => { - Command::new("nix-env") - .arg("-p") - .arg(&activate_opts.profile_path) - .arg("--rollback") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - let c = Command::new("nix-env") - .arg("-p") - .arg(&activate_opts.profile_path) - .arg("--list-generations") - .output() - .await?; - let generations_list = String::from_utf8(c.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); - - Command::new("nix-env") - .arg("-p") - .arg(&activate_opts.profile_path) - .arg("--delete-generations") - .arg(last_generation_id) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - // TODO: Find some way to make sure this command never changes, otherwise this will not work - Command::new("bash") - .arg("-c") - .arg(&activate_cmd) - .spawn()? - .await?; - - good_panic!("Failed to execute activation command"); - } - _ => {} - } - } + activate_opts.auto_rollback, + ) + .await?; } } diff --git a/src/utils/activate.rs b/src/utils/activate.rs new file mode 100644 index 0000000..33774fd --- /dev/null +++ b/src/utils/activate.rs @@ -0,0 +1,108 @@ +use std::process::Stdio; +use tokio::process::Command; + +use std::path::Path; + +pub async fn activate( + profile_path: String, + closure: String, + activate_cmd: Option, + bootstrap_cmd: Option, + auto_rollback: bool, +) -> Result<(), Box> { + info!("Activating profile"); + + Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--set") + .arg(&closure) + .stdout(Stdio::null()) + .spawn()? + .await?; + + if let (Some(bootstrap_cmd), false) = (bootstrap_cmd, !Path::new(&profile_path).exists()) { + let bootstrap_status = Command::new("bash") + .arg("-c") + .arg(&bootstrap_cmd) + .env("PROFILE", &profile_path) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await; + + match bootstrap_status { + Ok(s) if s.success() => (), + _ => { + tokio::fs::remove_file(&profile_path).await?; + good_panic!("Failed to execute bootstrap command"); + } + } + } + + if let Some(activate_cmd) = activate_cmd { + let activate_status = Command::new("bash") + .arg("-c") + .arg(&activate_cmd) + .env("PROFILE", &profile_path) + .status() + .await; + + match activate_status { + Ok(s) if s.success() => (), + _ if auto_rollback => { + Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--rollback") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + let c = Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--list-generations") + .output() + .await?; + let generations_list = String::from_utf8(c.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); + + Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--delete-generations") + .arg(last_generation_id) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + // TODO: Find some way to make sure this command never changes, otherwise this will not work + Command::new("bash") + .arg("-c") + .arg(&activate_cmd) + .spawn()? + .await?; + + good_panic!("Failed to execute activation command"); + } + _ => {} + } + } + + Ok(()) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 764e2e9..935f470 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -1,10 +1,7 @@ use std::borrow::Cow; use std::path::PathBuf; -pub mod data; -pub mod deploy; -pub mod push; - +#[macro_export] macro_rules! good_panic { ($($tts:tt)*) => {{ error!($($tts)*); @@ -12,6 +9,73 @@ macro_rules! good_panic { }} } +pub mod activate; +pub mod data; +pub mod deploy; +pub mod push; + +#[derive(PartialEq, Debug)] +pub struct DeployFlake<'a> { + pub repo: &'a str, + pub node: Option<&'a str>, + pub profile: Option<&'a str>, +} + +pub fn parse_flake(flake: &str) -> DeployFlake { + let flake_fragment_start = flake.find('#'); + let (repo, maybe_fragment) = match flake_fragment_start { + Some(s) => (&flake[..s], Some(&flake[s + 1..])), + None => (flake, None), + }; + + let (node, profile) = match maybe_fragment { + Some(fragment) => { + let fragment_profile_start = fragment.find('.'); + match fragment_profile_start { + Some(s) => (Some(&fragment[..s]), Some(&fragment[s + 1..])), + None => (Some(fragment), None), + } + } + None => (None, None), + }; + + DeployFlake { + repo, + node, + profile, + } +} + +#[test] +fn test_parse_flake() { + assert_eq!( + parse_flake("../deploy/examples/system#example"), + DeployFlake { + repo: "../deploy/examples/system", + node: Some("example"), + profile: None + } + ); + + assert_eq!( + parse_flake("../deploy/examples/system#example.system"), + DeployFlake { + repo: "../deploy/examples/system", + node: Some("example"), + profile: Some("system") + } + ); + + assert_eq!( + parse_flake("../deploy/examples/system"), + DeployFlake { + repo: "../deploy/examples/system", + node: None, + profile: None, + } + ); +} + pub struct DeployData<'a> { pub sudo: Option, pub ssh_user: Cow<'a, str>, -- cgit v1.2.3 From a22063343e54da9f589c7235f2f64b57fe5c257b Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 15:12:42 -0700 Subject: More functions --- src/main.rs | 89 +++++++++++++++++++++++++++++------------------------ src/utils/data.rs | 2 +- src/utils/deploy.rs | 1 - 3 files changed, 50 insertions(+), 42 deletions(-) diff --git a/src/main.rs b/src/main.rs index cde4fc3..e084f0f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -156,64 +156,73 @@ async fn deploy_all_profiles( Ok(()) } -#[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(); - - match opts.subcmd { - SubCommand::Deploy(deploy_opts) => { - let deploy_flake = utils::parse_flake(deploy_opts.flake.as_str()); +async fn test_flake_support() -> Result> { + Ok(Command::new("nix") + .arg("eval") + .arg("--expr") + .arg("builtins.getFlake") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await? + .success()) +} - let test_flake_status = Command::new("nix") +async fn get_deployment_data( + supports_flakes: bool, + repo: &str, +) -> Result> { + let data_json = match supports_flakes { + true => { + let c = Command::new("nix") .arg("eval") - .arg("--expr") - .arg("builtins.getFlake") + .arg("--json") + .arg(format!("{}#deploy", repo)) .stdout(Stdio::null()) .stderr(Stdio::null()) - .status() + // TODO forward input args? + .output() .await?; - let supports_flakes = test_flake_status.success(); - - let data_json = match supports_flakes { - true => { - let c = Command::new("nix") - .arg("eval") - .arg("--json") - .arg(format!("{}#deploy", deploy_flake.repo)) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - // TODO forward input args? - .output() - .await?; - - String::from_utf8(c.stdout)? - } - false => { - let c = Command::new("nix-instanciate") + String::from_utf8(c.stdout)? + } + false => { + let c = Command::new("nix-instanciate") .arg("--strict") .arg("--read-write-mode") .arg("--json") .arg("--eval") .arg("--E") - .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", deploy_flake.repo)) + .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) .stdout(Stdio::null()) .stderr(Stdio::null()) .output() .await?; - String::from_utf8(c.stdout)? - } - }; + String::from_utf8(c.stdout)? + } + }; + + Ok(serde_json::from_str(&data_json)?) +} +#[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(); + + 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: utils::data::Data = serde_json::from_str(&data_json)?; + let data = get_deployment_data(supports_flakes, deploy_flake.repo).await?; match (deploy_flake.node, deploy_flake.profile) { (Some(node_name), Some(profile_name)) => { diff --git a/src/utils/data.rs b/src/utils/data.rs index 779d913..b28b6cd 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -1,6 +1,6 @@ use merge::Merge; -use std::{collections::HashMap}; +use std::collections::HashMap; #[derive(Deserialize, Debug, Clone, Merge)] pub struct GenericSettings { diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 7260e55..900743c 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -1,6 +1,5 @@ use super::data; - use tokio::process::Command; pub async fn deploy_profile( -- cgit v1.2.3 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 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.2.3 From edaed825650eea32878441d3b8c7eb40e8877882 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 16:17:31 -0700 Subject: Add examples --- .gitignore | 3 ++- examples/simple/flake.lock | 25 +++++++++++++++++++++ examples/simple/flake.nix | 23 ++++++++++++++++++++ examples/system/README.md | 10 +++++++++ examples/system/bare.nix | 6 +++++ examples/system/common.nix | 30 +++++++++++++++++++++++++ examples/system/configuration.nix | 11 ++++++++++ examples/system/flake.lock | 25 +++++++++++++++++++++ examples/system/flake.nix | 46 +++++++++++++++++++++++++++++++++++++++ examples/system/hello.nix | 27 +++++++++++++++++++++++ examples/system/nix-pub.pem | 1 + examples/system/nix.key | 1 + src/main.rs | 2 ++ 13 files changed, 209 insertions(+), 1 deletion(-) create mode 100644 examples/simple/flake.lock create mode 100644 examples/simple/flake.nix create mode 100644 examples/system/README.md create mode 100644 examples/system/bare.nix create mode 100644 examples/system/common.nix create mode 100644 examples/system/configuration.nix create mode 100644 examples/system/flake.lock create mode 100644 examples/system/flake.nix create mode 100644 examples/system/hello.nix create mode 100644 examples/system/nix-pub.pem create mode 100644 examples/system/nix.key diff --git a/.gitignore b/.gitignore index d787b70..5a39c3d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -/result +result +/examples/system/bare-system.qcow2 \ No newline at end of file diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock new file mode 100644 index 0000000..a57ff9d --- /dev/null +++ b/examples/simple/flake.lock @@ -0,0 +1,25 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1592491430, + "narHash": "sha256-7WNpr16iUyjG4caad137nCqxXNTdct202jy05lslZXA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "07299ff81e58e16b282fe602ce5e629854dfd544", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/examples/simple/flake.nix b/examples/simple/flake.nix new file mode 100644 index 0000000..6e516d3 --- /dev/null +++ b/examples/simple/flake.nix @@ -0,0 +1,23 @@ +{ + description = "Deploy GNU hello to localhost"; + + outputs = { self, nixpkgs }: { + deploy.nodes.example = { + hostname = "localhost"; + profiles.hello = { + user = "test_deploy"; + 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 ${ + pkgs.writeText "deploy.json" (builtins.toJSON self.deploy) + } ${../../interface/deploy.json} && touch $out"; + }) + nixpkgs.legacyPackages; + }; +} diff --git a/examples/system/README.md b/examples/system/README.md new file mode 100644 index 0000000..1dea41f --- /dev/null +++ b/examples/system/README.md @@ -0,0 +1,10 @@ +# 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. + +1. Run bare system from `.#nixosConfigurations.bare` + - `nix build .#nixosConfigurations.bare.config.system.build.vm` + - `QEMU_NET_OPTS=hostfwd=tcp::2221-:22 ./result/bin/run-bare-system-vm` +2. `nix run github:serokell/deploy --prime` +3. ??? +4. PROFIT!!! diff --git a/examples/system/bare.nix b/examples/system/bare.nix new file mode 100644 index 0000000..282080f --- /dev/null +++ b/examples/system/bare.nix @@ -0,0 +1,6 @@ +{ + imports = [ ./common.nix ]; + + # Use that when deploy scripts asks you for a hostname + networking.hostName = "bare-system"; +} diff --git a/examples/system/common.nix b/examples/system/common.nix new file mode 100644 index 0000000..7e7448e --- /dev/null +++ b/examples/system/common.nix @@ -0,0 +1,30 @@ +{ + boot.loader.systemd-boot.enable = true; + + fileSystems."/" = { + device = "/dev/disk/by-uuid/00000000-0000-0000-0000-000000000000"; + fsType = "btrfs"; + }; + + users.users.admin = { + isNormalUser = true; + extraGroups = [ "wheel" "sudo" ]; + password = "123"; + }; + + services.openssh = { enable = true; }; + + # Another option would be root on the server + security.sudo.extraRules = [{ + groups = [ "wheel" ]; + commands = [{ + command = "ALL"; + options = [ "NOPASSWD" ]; + }]; + }]; + + nix.binaryCachePublicKeys = [ + (builtins.readFile ./nix-pub.pem) + "cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY=" + ]; +} diff --git a/examples/system/configuration.nix b/examples/system/configuration.nix new file mode 100644 index 0000000..b2b55cf --- /dev/null +++ b/examples/system/configuration.nix @@ -0,0 +1,11 @@ +{ + imports = [ ./common.nix ]; + + networking.hostName = "example-nixos-syyyystem"; + + users.users.hello = { + isNormalUser = true; + password = ""; + uid = 1010; + }; +} diff --git a/examples/system/flake.lock b/examples/system/flake.lock new file mode 100644 index 0000000..a57ff9d --- /dev/null +++ b/examples/system/flake.lock @@ -0,0 +1,25 @@ +{ + "nodes": { + "nixpkgs": { + "locked": { + "lastModified": 1592491430, + "narHash": "sha256-7WNpr16iUyjG4caad137nCqxXNTdct202jy05lslZXA=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "07299ff81e58e16b282fe602ce5e629854dfd544", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "root": { + "inputs": { + "nixpkgs": "nixpkgs" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/examples/system/flake.nix b/examples/system/flake.nix new file mode 100644 index 0000000..383960b --- /dev/null +++ b/examples/system/flake.nix @@ -0,0 +1,46 @@ +{ + 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"; + modules = [ ./configuration.nix ]; + }; + + nixosConfigurations.bare = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; + }; + + # This is the application we actually want to run + defaultPackage.x86_64-linux = import ./hello.nix nixpkgs; + + deploy.nodes.example = { + sshOpts = [ "-p" "2221" ]; + hostname = "localhost"; + fastConnection = true; + profiles = { + system = { + sshUser = "admin"; + activate = "$PROFILE/bin/switch-to-configuration switch"; + path = self.nixosConfigurations.example-nixos-system.config.system.build.toplevel; + user = "root"; + }; + hello = { + sshUser = "hello"; + activate = "$PROFILE/bin/activate"; + path = self.defaultPackage.x86_64-linux; + user = "hello"; + }; + }; + }; + + 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"; + }) nixpkgs.legacyPackages; + }; +} diff --git a/examples/system/hello.nix b/examples/system/hello.nix new file mode 100644 index 0000000..8c207f1 --- /dev/null +++ b/examples/system/hello.nix @@ -0,0 +1,27 @@ +nixpkgs: +let + pkgs = nixpkgs.legacyPackages.x86_64-linux; + generateSystemd = type: name: config: + (nixpkgs.lib.nixosSystem { + modules = [{ systemd."${type}s".${name} = config; }]; + system = "x86_64-linux"; + }).config.systemd.units."${name}.${type}".text; + + mkService = generateSystemd "service"; + + service = pkgs.writeTextFile { + name = "hello.service"; + text = mkService "hello" { + unitConfig.WantedBy = [ "multi-user.target" ]; + path = [ pkgs.hello ]; + script = "hello -g lel; touch $HOME/oof"; + }; + }; +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 + systemctl --user daemon-reload + systemctl --user restart hello +'' diff --git a/examples/system/nix-pub.pem b/examples/system/nix-pub.pem new file mode 100644 index 0000000..926f44c --- /dev/null +++ b/examples/system/nix-pub.pem @@ -0,0 +1 @@ +cache.example.com:ic28PY7OIOQtoU282iaiizvA5WIOtYx5h6c9ePn3hDQ= \ No newline at end of file diff --git a/examples/system/nix.key b/examples/system/nix.key new file mode 100644 index 0000000..9157587 --- /dev/null +++ b/examples/system/nix.key @@ -0,0 +1 @@ +cache.example.com:dPNdwv04QPIEpcWnGioZmX9dvaGe7GCo7BZJFymDBnSJzbw9js4g5C2hTbzaJqKLO8DlYg61jHmHpz14+feENA== \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 6dcb885..513fbf2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -123,6 +123,7 @@ async fn deploy_all_profiles( } /// Returns if the available Nix installation supports flakes +#[inline] async fn test_flake_support() -> Result> { Ok(Command::new("nix") .arg("eval") @@ -136,6 +137,7 @@ async fn test_flake_support() -> Result> { } /// Evaluates the Nix in the given `repo` and return the processed Data from it +#[inline] async fn get_deployment_data( supports_flakes: bool, repo: &str, -- cgit v1.2.3 From 0f6699882316676fe114e7e7bda61a01bd647fca Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 28 Sep 2020 16:35:27 -0700 Subject: add README.md --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..4580a42 --- /dev/null +++ b/README.md @@ -0,0 +1,24 @@ +# deploy-rs +#### A candidate for [serokell/deploy](https://github.com/serokell/deploy) + +**This is very early development software, you should expect to find issues** + +## Usage examples + +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` + +## 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. + +This type of design (as opposed to more traditional tools like NixOps or morph) allows for lesser-privileged deployments, and the ability to update different things independently of eachother. + +## Things to work on + +- ~~Ordered profiles~~ +- Automatic rollbacks if one profile on node failed to deploy (partially implemented) +- UI (?) \ No newline at end of file -- cgit v1.2.3 From 93a04f7e3037f69bdeab777d2fc6c4fb37795f4e Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 29 Sep 2020 12:36:26 -0700 Subject: Pass extra arguments to the Nix build command --- src/main.rs | 52 ++++++++++++++++++++++++++++++++-------------------- 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/src/main.rs b/src/main.rs index 513fbf2..668b697 100644 --- a/src/main.rs +++ b/src/main.rs @@ -25,6 +25,8 @@ struct Opts { /// Check signatures when using `nix copy` #[clap(short, long)] checksigs: bool, + /// Extra arguments to be passed to nix build + extra_build_args: Vec, } #[inline] @@ -141,38 +143,47 @@ async fn test_flake_support() -> Result> { async fn get_deployment_data( supports_flakes: bool, repo: &str, + extra_build_args: Vec, ) -> Result> { - let data_json = match supports_flakes { - true => { - let c = Command::new("nix") - .arg("eval") - .arg("--json") - .arg(format!("{}#deploy", repo)) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - // TODO forward input args? - .output() - .await?; + let mut c = match supports_flakes { + true => Command::new("nix"), + false => Command::new("nix-instanciate"), + }; - String::from_utf8(c.stdout)? + let mut build_command = match supports_flakes { + true => { + c.arg("eval").arg("--json").arg(format!("{}#deploy", repo)) } false => { - let c = Command::new("nix-instanciate") + c .arg("--strict") .arg("--read-write-mode") .arg("--json") .arg("--eval") .arg("--E") .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .output() - .await?; - - String::from_utf8(c.stdout)? } }; + for extra_arg in extra_build_args { + build_command = build_command.arg(extra_arg); + } + + let build_output = build_command + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .output() + .await?; + + if !build_output.status.success() { + good_panic!( + "Error building deploy props for the provided flake: {}", + repo + ); + } + + let data_json = String::from_utf8(build_output.stdout)?; + Ok(serde_json::from_str(&data_json)?) } #[tokio::main] @@ -189,7 +200,8 @@ async fn main() -> Result<(), Box> { let supports_flakes = test_flake_support().await?; - let data = get_deployment_data(supports_flakes, deploy_flake.repo).await?; + let data = + get_deployment_data(supports_flakes, deploy_flake.repo, opts.extra_build_args).await?; match (deploy_flake.node, deploy_flake.profile) { (Some(node_name), Some(profile_name)) => { -- cgit v1.2.3 From e3c55575ca6bfd0c9166c52b4aac76b3761bb313 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 29 Sep 2020 12:40:32 -0700 Subject: Move all activation logic to activate.rs (the unused warnings got annoying) --- src/activate.rs | 110 +++++++++++++++++++++++++++++++++++++++++++++++++- src/utils/activate.rs | 108 ------------------------------------------------- src/utils/mod.rs | 1 - 3 files changed, 109 insertions(+), 110 deletions(-) delete mode 100644 src/utils/activate.rs diff --git a/src/activate.rs b/src/activate.rs index c446ee0..8e22aea 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -1,5 +1,10 @@ use clap::Clap; +use std::process::Stdio; +use tokio::process::Command; + +use std::path::Path; + extern crate pretty_env_logger; #[macro_use] extern crate log; @@ -30,6 +35,109 @@ struct Opts { auto_rollback: bool, } +pub async fn activate( + profile_path: String, + closure: String, + activate_cmd: Option, + bootstrap_cmd: Option, + auto_rollback: bool, +) -> Result<(), Box> { + info!("Activating profile"); + + Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--set") + .arg(&closure) + .stdout(Stdio::null()) + .spawn()? + .await?; + + if let (Some(bootstrap_cmd), false) = (bootstrap_cmd, !Path::new(&profile_path).exists()) { + let bootstrap_status = Command::new("bash") + .arg("-c") + .arg(&bootstrap_cmd) + .env("PROFILE", &profile_path) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .status() + .await; + + match bootstrap_status { + Ok(s) if s.success() => (), + _ => { + tokio::fs::remove_file(&profile_path).await?; + good_panic!("Failed to execute bootstrap command"); + } + } + } + + if let Some(activate_cmd) = activate_cmd { + let activate_status = Command::new("bash") + .arg("-c") + .arg(&activate_cmd) + .env("PROFILE", &profile_path) + .status() + .await; + + match activate_status { + Ok(s) if s.success() => (), + _ if auto_rollback => { + Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--rollback") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + let c = Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--list-generations") + .output() + .await?; + let generations_list = String::from_utf8(c.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); + + Command::new("nix-env") + .arg("-p") + .arg(&profile_path) + .arg("--delete-generations") + .arg(last_generation_id) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn()? + .await?; + + // TODO: Find some way to make sure this command never changes, otherwise this will not work + Command::new("bash") + .arg("-c") + .arg(&activate_cmd) + .spawn()? + .await?; + + good_panic!("Failed to execute activation command"); + } + _ => {} + } + } + + Ok(()) +} #[tokio::main] async fn main() -> Result<(), Box> { @@ -41,7 +149,7 @@ async fn main() -> Result<(), Box> { let opts: Opts = Opts::parse(); - utils::activate::activate( + activate( opts.profile_path, opts.closure, opts.activate_cmd, diff --git a/src/utils/activate.rs b/src/utils/activate.rs deleted file mode 100644 index 33774fd..0000000 --- a/src/utils/activate.rs +++ /dev/null @@ -1,108 +0,0 @@ -use std::process::Stdio; -use tokio::process::Command; - -use std::path::Path; - -pub async fn activate( - profile_path: String, - closure: String, - activate_cmd: Option, - bootstrap_cmd: Option, - auto_rollback: bool, -) -> Result<(), Box> { - info!("Activating profile"); - - Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--set") - .arg(&closure) - .stdout(Stdio::null()) - .spawn()? - .await?; - - if let (Some(bootstrap_cmd), false) = (bootstrap_cmd, !Path::new(&profile_path).exists()) { - let bootstrap_status = Command::new("bash") - .arg("-c") - .arg(&bootstrap_cmd) - .env("PROFILE", &profile_path) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status() - .await; - - match bootstrap_status { - Ok(s) if s.success() => (), - _ => { - tokio::fs::remove_file(&profile_path).await?; - good_panic!("Failed to execute bootstrap command"); - } - } - } - - if let Some(activate_cmd) = activate_cmd { - let activate_status = Command::new("bash") - .arg("-c") - .arg(&activate_cmd) - .env("PROFILE", &profile_path) - .status() - .await; - - match activate_status { - Ok(s) if s.success() => (), - _ if auto_rollback => { - Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--rollback") - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - let c = Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--list-generations") - .output() - .await?; - let generations_list = String::from_utf8(c.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); - - Command::new("nix-env") - .arg("-p") - .arg(&profile_path) - .arg("--delete-generations") - .arg(last_generation_id) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .spawn()? - .await?; - - // TODO: Find some way to make sure this command never changes, otherwise this will not work - Command::new("bash") - .arg("-c") - .arg(&activate_cmd) - .spawn()? - .await?; - - good_panic!("Failed to execute activation command"); - } - _ => {} - } - } - - Ok(()) -} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 935f470..8861692 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -9,7 +9,6 @@ macro_rules! good_panic { }} } -pub mod activate; pub mod data; pub mod deploy; pub mod push; -- cgit v1.2.3 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 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.2.3 From a0328dbcf76b7c551e92fd25060cfc7d7e4d9ebe Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 29 Sep 2020 21:27:49 -0700 Subject: More separation and component testing --- src/main.rs | 57 +++++++++++++++----------- src/utils/deploy.rs | 116 +++++++++++++++++++++++++++++++++++++++------------- 2 files changed, 122 insertions(+), 51 deletions(-) diff --git a/src/main.rs b/src/main.rs index 668b697..f8b03a3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -143,7 +143,7 @@ async fn test_flake_support() -> Result> { async fn get_deployment_data( supports_flakes: bool, repo: &str, - extra_build_args: Vec, + extra_build_args: &[String], ) -> Result> { let mut c = match supports_flakes { true => Command::new("nix"), @@ -156,12 +156,12 @@ async fn get_deployment_data( } false => { c - .arg("--strict") - .arg("--read-write-mode") - .arg("--json") - .arg("--eval") - .arg("--E") - .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) + .arg("--strict") + .arg("--read-write-mode") + .arg("--json") + .arg("--eval") + .arg("--E") + .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) } }; @@ -186,23 +186,13 @@ async fn get_deployment_data( Ok(serde_json::from_str(&data_json)?) } -#[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(); - - 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, opts.extra_build_args).await?; +async fn run_deploy( + deploy_flake: utils::DeployFlake<'_>, + data: utils::data::Data, + supports_flakes: bool, + opts: &Opts, +) -> Result<(), Box> { match (deploy_flake.node, deploy_flake.profile) { (Some(node_name), Some(profile_name)) => { let node = match data.nodes.get(node_name) { @@ -289,3 +279,24 @@ async fn main() -> Result<(), Box> { Ok(()) } +#[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(); + + 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, &opts.extra_build_args).await?; + + run_deploy(deploy_flake, data, supports_flakes, &opts).await?; + + Ok(()) +} diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 42bd0b4..c44c0d3 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -2,47 +2,54 @@ use super::data; use tokio::process::Command; -pub async fn deploy_profile( - profile: &data::Profile, - profile_name: &str, - node: &data::Node, - node_name: &str, - merged_settings: &data::GenericSettings, - deploy_data: &super::DeployData<'_>, - auto_rollback: bool, -) -> Result<(), Box> { - info!( - "Activating profile `{}` for node `{}`", - profile_name, node_name - ); - - let mut self_activate_command = format!( - "{} '{}' '{}'", - deploy_data - .current_exe - .as_path() +fn deploy_path_to_activate_path_str( + deploy_path: &std::path::Path, +) -> Result> { + Ok(format!( + "{}/activate", + deploy_path .parent() - .unwrap() + .ok_or("Deploy path too short")? .to_str() - .unwrap() + .ok_or("Deploy path is not valid utf8")? .to_owned() - + "/activate", - deploy_data.profile_path, - profile.profile_settings.path, - ); + )) +} - if let Some(sudo_cmd) = &deploy_data.sudo { +#[test] +fn test_activate_path_generation() { + match deploy_path_to_activate_path_str(&std::path::PathBuf::from( + "/blah/blah/deploy-rs/bin/deploy", + )) { + Err(_) => panic!(""), + Ok(x) => assert_eq!(x, "/blah/blah/deploy-rs/bin/activate".to_string()), + } +} + +fn build_activate_command( + activate_path_str: String, + sudo: &Option, + profile_path: &str, + closure: &str, + activate_cmd: &Option, + bootstrap_cmd: &Option, + auto_rollback: bool, +) -> Result> { + let mut self_activate_command = + format!("{} '{}' '{}'", activate_path_str, profile_path, closure); + + if let Some(sudo_cmd) = &sudo { self_activate_command = format!("{} {}", sudo_cmd, self_activate_command); } - if let Some(ref bootstrap_cmd) = profile.profile_settings.bootstrap { + if let Some(ref bootstrap_cmd) = bootstrap_cmd { self_activate_command = format!( "{} --bootstrap-cmd '{}'", self_activate_command, bootstrap_cmd ); } - if let Some(ref activate_cmd) = profile.profile_settings.activate { + if let Some(ref activate_cmd) = activate_cmd { self_activate_command = format!( "{} --activate-cmd '{}'", self_activate_command, activate_cmd @@ -53,6 +60,59 @@ pub async fn deploy_profile( self_activate_command = format!("{} --auto-rollback", self_activate_command); } + Ok(self_activate_command) +} + +#[test] +fn test_activation_command_builder() { + let activate_path_str = "/blah/bin/activate".to_string(); + let sudo = Some("sudo -u test".to_string()); + let profile_path = "/blah/profiles/test"; + let closure = "/blah/etc"; + let activate_cmd = Some("$THING/bin/aaaaaaa".to_string()); + let bootstrap_cmd = None; + let auto_rollback = true; + + match build_activate_command( + activate_path_str, + &sudo, + profile_path, + closure, + &activate_cmd, + &bootstrap_cmd, + auto_rollback, + ) { + Err(_) => panic!(""), + Ok(x) => assert_eq!(x, "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' --activate-cmd '$THING/bin/aaaaaaa' --auto-rollback".to_string()), + } +} + +pub async fn deploy_profile( + profile: &data::Profile, + profile_name: &str, + node: &data::Node, + node_name: &str, + merged_settings: &data::GenericSettings, + deploy_data: &super::DeployData<'_>, + auto_rollback: bool, +) -> Result<(), Box> { + info!( + "Activating profile `{}` for node `{}`", + profile_name, node_name + ); + + let activate_path_str = deploy_path_to_activate_path_str(&deploy_data.current_exe)?; + + let self_activate_command = build_activate_command( + activate_path_str, + &deploy_data.sudo, + &deploy_data.profile_path, + &profile.profile_settings.path, + &profile.profile_settings.activate, + &profile.profile_settings.bootstrap, + auto_rollback, + )?; + let mut c = Command::new("ssh"); let mut ssh_command = c.arg(format!( "ssh://{}@{}", -- cgit v1.2.3 From ea5aab76849ba3ce9ff2b7eba2a391d4ea33fa3a Mon Sep 17 00:00:00 2001 From: notgne2 Date: Thu, 1 Oct 2020 12:43:33 -0700 Subject: Improve nix copy stuff --- src/main.rs | 2 ++ src/utils/deploy.rs | 26 +------------------------- src/utils/mod.rs | 24 ++++++++++++++++++++++++ src/utils/push.rs | 10 ++++++---- 4 files changed, 33 insertions(+), 29 deletions(-) diff --git a/src/main.rs b/src/main.rs index 75842e5..ba87e46 100644 --- a/src/main.rs +++ b/src/main.rs @@ -149,6 +149,8 @@ async fn get_deployment_data( repo: &str, extra_build_args: &[String], ) -> Result> { + info!("Evaluating flake in {}", repo); + let mut c = match supports_flakes { true => Command::new("nix"), false => Command::new("nix-instanciate"), diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 9c258fd..1abae64 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -6,30 +6,6 @@ use super::data; use tokio::process::Command; -fn deploy_path_to_activate_path_str( - deploy_path: &std::path::Path, -) -> Result> { - Ok(format!( - "{}/activate", - deploy_path - .parent() - .ok_or("Deploy path too short")? - .to_str() - .ok_or("Deploy path is not valid utf8")? - .to_owned() - )) -} - -#[test] -fn test_activate_path_generation() { - match deploy_path_to_activate_path_str(&std::path::PathBuf::from( - "/blah/blah/deploy-rs/bin/deploy", - )) { - Err(_) => panic!(""), - Ok(x) => assert_eq!(x, "/blah/blah/deploy-rs/bin/activate".to_string()), - } -} - fn build_activate_command( activate_path_str: String, sudo: &Option, @@ -105,7 +81,7 @@ pub async fn deploy_profile( profile_name, node_name ); - let activate_path_str = deploy_path_to_activate_path_str(&deploy_data.current_exe)?; + let activate_path_str = super::deploy_path_to_activate_path_str(&deploy_data.current_exe)?; let self_activate_command = build_activate_command( activate_path_str, diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 5802627..30201c3 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -136,3 +136,27 @@ pub async fn make_deploy_data<'a>( current_exe, }) } + +pub fn deploy_path_to_activate_path_str( + deploy_path: &std::path::Path, +) -> Result> { + Ok(format!( + "{}/activate", + deploy_path + .parent() + .ok_or("Deploy path too short")? + .to_str() + .ok_or("Deploy path is not valid utf8")? + .to_owned() + )) +} + +#[test] +fn test_activate_path_generation() { + match deploy_path_to_activate_path_str(&std::path::PathBuf::from( + "/blah/blah/deploy-rs/bin/deploy", + )) { + Err(_) => panic!(""), + Ok(x) => assert_eq!(x, "/blah/blah/deploy-rs/bin/activate".to_string()), + } +} diff --git a/src/utils/push.rs b/src/utils/push.rs index c87c32b..38a576f 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -66,7 +66,9 @@ pub async fn push_profile( .arg("-k") .arg(local_key) .arg(&profile.profile_settings.path) - .arg(&deploy_data.current_exe) + .arg(&super::deploy_path_to_activate_path_str( + &deploy_data.current_exe, + )?) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn()? @@ -101,10 +103,10 @@ pub async fn push_profile( deploy_data.ssh_user, node.node_settings.hostname )) .arg(&profile.profile_settings.path) - .arg(&deploy_data.current_exe) + .arg(&super::deploy_path_to_activate_path_str( + &deploy_data.current_exe, + )?) .env("NIX_SSHOPTS", ssh_opts_str) - .stdout(Stdio::null()) - .stderr(Stdio::null()) .spawn()? .await?; -- cgit v1.2.3 From e14acaf2bdc14bbdc30f3d558b62f64fe33ff5f9 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Thu, 1 Oct 2020 18:21:40 -0700 Subject: Rework system for deploy properties, add CLI override flags --- src/main.rs | 140 ++++++++++++++++++++++++++++++-------------- src/utils/deploy.rs | 37 +++++------- src/utils/mod.rs | 166 +++++++++++++++++++++++++++++++++++++++------------- src/utils/push.rs | 46 +++++++-------- 4 files changed, 260 insertions(+), 129 deletions(-) diff --git a/src/main.rs b/src/main.rs index ba87e46..0d80e42 100644 --- a/src/main.rs +++ b/src/main.rs @@ -31,6 +31,25 @@ struct Opts { checksigs: bool, /// Extra arguments to be passed to nix build extra_build_args: Vec, + + /// Override the SSH user with the given value + #[clap(long)] + ssh_user: Option, + /// Override the profile user with the given value + #[clap(long)] + profile_user: Option, + /// Override the SSH options used + #[clap(long)] + ssh_opts: Option, + /// Override if the connecting to the target node should be considered fast + #[clap(long)] + fast_connection: Option, + /// Override if a rollback should be attempted if activation fails + #[clap(long)] + auto_rollback: Option, + /// Override hostname used for the node + #[clap(long)] + hostname: Option, } #[inline] @@ -41,6 +60,7 @@ async fn push_all_profiles( repo: &str, top_settings: &utils::data::GenericSettings, check_sigs: bool, + cmd_overrides: &utils::CmdOverrides, ) -> Result<(), Box> { info!("Pushing all profiles for `{}`", node_name); @@ -63,19 +83,23 @@ async fn push_all_profiles( 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, + let deploy_data = utils::make_deploy_data( + top_settings, node, node_name, + profile, + profile_name, + cmd_overrides, + )?; + + let deploy_defs = deploy_data.defs(); + + utils::push::push_profile( supports_flakes, check_sigs, repo, - &merged_settings, &deploy_data, + &deploy_defs, ) .await?; } @@ -88,6 +112,7 @@ async fn deploy_all_profiles( node: &utils::data::Node, node_name: &str, top_settings: &utils::data::GenericSettings, + cmd_overrides: &utils::CmdOverrides, ) -> Result<(), Box> { info!("Deploying all profiles for `{}`", node_name); @@ -110,19 +135,18 @@ async fn deploy_all_profiles( 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::deploy::deploy_profile( - profile, - profile_name, + let deploy_data = utils::make_deploy_data( + top_settings, node, node_name, - &merged_settings, - &deploy_data, - merged_settings.auto_rollback, - ) - .await?; + profile, + profile_name, + cmd_overrides, + )?; + + let deploy_defs = deploy_data.defs(); + + utils::deploy::deploy_profile(&deploy_data, &deploy_defs).await?; } Ok(()) @@ -197,7 +221,8 @@ async fn run_deploy( deploy_flake: utils::DeployFlake<'_>, data: utils::data::Data, supports_flakes: bool, - opts: &Opts, + check_sigs: bool, + cmd_overrides: utils::CmdOverrides, ) -> Result<(), Box> { match (deploy_flake.node, deploy_flake.profile) { (Some(node_name), Some(profile_name)) => { @@ -210,36 +235,27 @@ async fn run_deploy( 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( + &data.generic_settings, + node, + node_name, + profile, + profile_name, + &cmd_overrides, + )?; - let deploy_data = - utils::make_deploy_data(profile_name, node_name, &merged_settings).await?; + let deploy_defs = deploy_data.defs(); utils::push::push_profile( - profile, - profile_name, - node, - node_name, supports_flakes, - opts.checksigs, + check_sigs, deploy_flake.repo, - &merged_settings, &deploy_data, + &deploy_defs, ) .await?; - utils::deploy::deploy_profile( - profile, - profile_name, - node, - node_name, - &merged_settings, - &deploy_data, - merged_settings.auto_rollback, - ) - .await?; + utils::deploy::deploy_profile(&deploy_data, &deploy_defs).await?; } (Some(node_name), None) => { let node = match data.nodes.get(node_name) { @@ -253,11 +269,12 @@ async fn run_deploy( supports_flakes, deploy_flake.repo, &data.generic_settings, - opts.checksigs, + check_sigs, + &cmd_overrides, ) .await?; - deploy_all_profiles(node, node_name, &data.generic_settings).await?; + deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides).await?; } (None, None) => { info!("Deploying all profiles on all nodes"); @@ -269,13 +286,15 @@ async fn run_deploy( supports_flakes, deploy_flake.repo, &data.generic_settings, - opts.checksigs, + check_sigs, + &cmd_overrides, ) .await?; } for (node_name, node) in &data.nodes { - deploy_all_profiles(node, node_name, &data.generic_settings).await?; + deploy_all_profiles(node, node_name, &data.generic_settings, &cmd_overrides) + .await?; } } (None, Some(_)) => { @@ -285,6 +304,7 @@ async fn run_deploy( Ok(()) } + #[tokio::main] async fn main() -> Result<(), Box> { if std::env::var("DEPLOY_LOG").is_err() { @@ -297,12 +317,42 @@ async fn main() -> Result<(), Box> { let deploy_flake = utils::parse_flake(opts.flake.as_str()); + let cmd_overrides = utils::CmdOverrides { + ssh_user: opts.ssh_user, + profile_user: opts.profile_user, + ssh_opts: opts.ssh_opts, + fast_connection: opts.fast_connection, + auto_rollback: opts.auto_rollback, + 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 = get_deployment_data(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await?; - run_deploy(deploy_flake, data, supports_flakes, &opts).await?; + run_deploy( + deploy_flake, + data, + supports_flakes, + opts.checksigs, + cmd_overrides, + ) + .await?; Ok(()) } diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 1abae64..d46f2db 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -2,8 +2,6 @@ // // SPDX-License-Identifier: MPL-2.0 -use super::data; - use tokio::process::Command; fn build_activate_command( @@ -68,38 +66,35 @@ fn test_activation_command_builder() { } pub async fn deploy_profile( - profile: &data::Profile, - profile_name: &str, - node: &data::Node, - node_name: &str, - merged_settings: &data::GenericSettings, deploy_data: &super::DeployData<'_>, - auto_rollback: bool, + deploy_defs: &super::DeployDefs<'_>, ) -> Result<(), Box> { info!( "Activating profile `{}` for node `{}`", - profile_name, node_name + deploy_data.profile_name, deploy_data.node_name ); - let activate_path_str = super::deploy_path_to_activate_path_str(&deploy_data.current_exe)?; + let activate_path_str = super::deploy_path_to_activate_path_str(&deploy_defs.current_exe)?; let self_activate_command = build_activate_command( activate_path_str, - &deploy_data.sudo, - &deploy_data.profile_path, - &profile.profile_settings.path, - &profile.profile_settings.activate, - &profile.profile_settings.bootstrap, - auto_rollback, + &deploy_defs.sudo, + &deploy_defs.profile_path, + &deploy_data.profile.profile_settings.path, + &deploy_data.profile.profile_settings.activate, + &deploy_data.profile.profile_settings.bootstrap, + deploy_data.merged_settings.auto_rollback, )?; + let hostname = match deploy_data.cmd_overrides.hostname { + Some(ref x) => x, + None => &deploy_data.node.node_settings.hostname, + }; + let mut c = Command::new("ssh"); - let mut ssh_command = c.arg(format!( - "ssh://{}@{}", - deploy_data.ssh_user, node.node_settings.hostname - )); + let mut ssh_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); - for ssh_opt in &merged_settings.ssh_opts { + for ssh_opt in &deploy_data.merged_settings.ssh_opts { ssh_command = ssh_command.arg(ssh_opt); } diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 30201c3..bfdbc5e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -5,6 +5,8 @@ use std::borrow::Cow; use std::path::PathBuf; +use merge::Merge; + #[macro_export] macro_rules! good_panic { ($($tts:tt)*) => {{ @@ -17,6 +19,40 @@ pub mod data; pub mod deploy; pub mod push; +pub struct CmdOverrides { + pub ssh_user: Option, + pub profile_user: Option, + pub ssh_opts: Option, + pub fast_connection: Option, + pub auto_rollback: Option, + 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, @@ -80,60 +116,110 @@ fn test_parse_flake() { } pub struct DeployData<'a> { - pub sudo: Option, + pub node_name: &'a str, + pub node: &'a data::Node, + pub profile_name: &'a str, + pub profile: &'a data::Profile, + + pub cmd_overrides: &'a CmdOverrides, + + pub merged_settings: data::GenericSettings, +} + +pub struct DeployDefs<'a> { pub ssh_user: Cow<'a, str>, pub profile_user: Cow<'a, str>, pub profile_path: String, pub current_exe: PathBuf, + pub sudo: Option, } -pub async fn make_deploy_data<'a>( - profile_name: &str, - node_name: &str, - merged_settings: &'a data::GenericSettings, -) -> Result, Box> { - let ssh_user: Cow = match &merged_settings.ssh_user { - Some(u) => u.into(), - None => whoami::username().into(), - }; - - let profile_user: Cow = match &merged_settings.user { - Some(x) => x.into(), - None => match &merged_settings.ssh_user { - Some(x) => x.into(), - None => good_panic!( - "Neither user nor sshUser set for profile `{}` of node `{}`", - profile_name, - node_name +impl<'a> DeployData<'a> { + pub fn defs(&'a self) -> DeployDefs<'a> { + let ssh_user: Cow = match self.merged_settings.ssh_user { + Some(ref u) => u.into(), + None => whoami::username().into(), + }; + + let profile_user: Cow = match self.merged_settings.user { + Some(ref x) => x.into(), + None => match self.merged_settings.ssh_user { + Some(ref x) => x.into(), + None => good_panic!( + "Neither user nor sshUser set for profile `{}` of node `{}`", + self.profile_name, + self.node_name + ), + }, + }; + + 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 = match &profile_user[..] { - "root" => format!("/nix/var/nix/profiles/{}", profile_name), - _ => format!( - "/nix/var/nix/profiles/per-user/{}/{}", - profile_user, profile_name - ), - }; + let sudo: Option = match self.merged_settings.user { + Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), + _ => None, + }; - let sudo: Option = match merged_settings.user { - Some(ref user) if user != &ssh_user => Some(format!("sudo -u {}", user)), - _ => None, - }; + let current_exe = + std::env::current_exe().expect("Expected to find current executable path"); + + if !current_exe.starts_with("/nix/store/") { + good_panic!("The deploy binary must be in the Nix store"); + } + + DeployDefs { + ssh_user, + profile_user, + profile_path, + current_exe, + sudo, + } + } +} - let current_exe = std::env::current_exe().expect("Expected to find current executable path"); +pub fn make_deploy_data<'a, 's>( + top_settings: &'s data::GenericSettings, + node: &'a data::Node, + node_name: &'a str, + profile: &'a data::Profile, + profile_name: &'a str, + cmd_overrides: &'a CmdOverrides, +) -> Result, Box> { + let mut merged_settings = top_settings.clone(); + merged_settings.merge(node.generic_settings.clone()); + merged_settings.merge(profile.generic_settings.clone()); - if !current_exe.starts_with("/nix/store/") { - good_panic!("The deploy binary must be in the Nix store"); + if cmd_overrides.ssh_user.is_some() { + merged_settings.ssh_user = cmd_overrides.ssh_user.clone(); + } + if cmd_overrides.profile_user.is_some() { + merged_settings.user = cmd_overrides.profile_user.clone(); + } + if let Some(ref ssh_opts) = cmd_overrides.ssh_opts { + 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; + } + if let Some(auto_rollback) = cmd_overrides.auto_rollback { + merged_settings.auto_rollback = auto_rollback; } Ok(DeployData { - sudo, - ssh_user, - profile_user, - profile_path, - current_exe, + profile, + profile_name, + node, + node_name, + + cmd_overrides, + + merged_settings, }) } diff --git a/src/utils/push.rs b/src/utils/push.rs index 38a576f..9a6748e 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -2,30 +2,24 @@ // // SPDX-License-Identifier: MPL-2.0 -use super::data; - use std::process::Stdio; use tokio::process::Command; pub async fn push_profile( - profile: &data::Profile, - profile_name: &str, - node: &data::Node, - node_name: &str, supports_flakes: bool, check_sigs: bool, repo: &str, - merged_settings: &data::GenericSettings, deploy_data: &super::DeployData<'_>, + deploy_defs: &super::DeployDefs<'_>, ) -> Result<(), Box> { info!( "Pushing profile `{}` for node `{}`", - profile_name, node_name + deploy_data.profile_name, deploy_data.node_name ); debug!( "Building profile `{} for node `{}`", - profile_name, node_name + deploy_data.profile_name, deploy_data.node_name ); if supports_flakes { @@ -34,7 +28,7 @@ pub async fn push_profile( .arg("--no-link") .arg(format!( "{}#deploy.nodes.{}.profiles.{}.path", - repo, node_name, profile_name + repo, deploy_data.node_name, deploy_data.profile_name )) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -46,7 +40,7 @@ pub async fn push_profile( .arg("-A") .arg(format!( "deploy.nodes.{}.profiles.{}.path", - node_name, profile_name + deploy_data.node_name, deploy_data.profile_name )) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -57,7 +51,7 @@ pub async fn push_profile( if let Ok(local_key) = std::env::var("LOCAL_KEY") { info!( "Signing key present! Signing profile `{}` for node `{}`", - profile_name, node_name + deploy_data.profile_name, deploy_data.node_name ); Command::new("nix") @@ -65,9 +59,9 @@ pub async fn push_profile( .arg("-r") .arg("-k") .arg(local_key) - .arg(&profile.profile_settings.path) + .arg(&deploy_data.profile.profile_settings.path) .arg(&super::deploy_path_to_activate_path_str( - &deploy_data.current_exe, + &deploy_defs.current_exe, )?) .stdout(Stdio::null()) .stderr(Stdio::null()) @@ -75,12 +69,15 @@ pub async fn push_profile( .await?; } - debug!("Copying profile `{} for node `{}`", profile_name, node_name); + debug!( + "Copying profile `{} for node `{}`", + deploy_data.profile_name, deploy_data.node_name + ); let mut copy_command_ = Command::new("nix"); let mut copy_command = copy_command_.arg("copy"); - if merged_settings.fast_connection { + if deploy_data.merged_settings.fast_connection { copy_command = copy_command.arg("--substitute-on-destination"); } @@ -88,7 +85,8 @@ pub async fn push_profile( copy_command = copy_command.arg("--no-check-sigs"); } - let ssh_opts_str = merged_settings + let ssh_opts_str = deploy_data + .merged_settings .ssh_opts // This should provide some extra safety, but it also breaks for some reason, oh well // .iter() @@ -96,15 +94,17 @@ pub async fn push_profile( // .collect::>() .join(" "); + let hostname = match deploy_data.cmd_overrides.hostname { + Some(ref x) => x, + None => &deploy_data.node.node_settings.hostname, + }; + copy_command .arg("--to") - .arg(format!( - "ssh://{}@{}", - deploy_data.ssh_user, node.node_settings.hostname - )) - .arg(&profile.profile_settings.path) + .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)) + .arg(&deploy_data.profile.profile_settings.path) .arg(&super::deploy_path_to_activate_path_str( - &deploy_data.current_exe, + &deploy_defs.current_exe, )?) .env("NIX_SSHOPTS", ssh_opts_str) .spawn()? -- cgit v1.2.3 From 05803e0ebaf417d9ba40645b6548a48bf51f9213 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Thu, 1 Oct 2020 20:24:09 -0700 Subject: Handle more command exits correctly --- src/activate.rs | 39 +++++++++++++++++++++++++++++---------- src/utils/deploy.rs | 6 +++++- src/utils/push.rs | 30 +++++++++++++++++++++--------- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/src/activate.rs b/src/activate.rs index 3c7c16d..f75303f 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -87,22 +87,33 @@ pub async fn activate( match activate_status { Ok(s) if s.success() => (), _ if auto_rollback => { - Command::new("nix-env") + 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()) - .spawn()? + .status() .await?; - let c = Command::new("nix-env") + if !nix_env_rollback_exit_status.success() { + good_panic!("`nix-env --rollback` failed"); + } + + let nix_env_list_generations_out = Command::new("nix-env") .arg("-p") .arg(&profile_path) .arg("--list-generations") .output() .await?; - let generations_list = String::from_utf8(c.stdout)?; + + 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() @@ -117,24 +128,32 @@ pub async fn activate( debug!("Removing generation entry {}", last_generation_line); warn!("Removing generation by ID {}", last_generation_id); - Command::new("nix-env") + 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()) - .spawn()? + .status() .await?; + if !nix_env_delete_generation_exit_status.success() { + good_panic!("Failed to delete failed generation"); + } + // TODO: Find some way to make sure this command never changes, otherwise this will not work - Command::new("bash") + let re_activate_exit_status = Command::new("bash") .arg("-c") .arg(&activate_cmd) - .spawn()? + .status() .await?; - good_panic!("Failed to execute activation command"); + if !re_activate_exit_status.success() { + good_panic!("Failed to re-activate the last generation"); + } + + std::process::exit(1); } _ => {} } @@ -163,4 +182,4 @@ async fn main() -> Result<(), Box> { .await?; Ok(()) -} \ No newline at end of file +} diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index d46f2db..f1f4210 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -98,7 +98,11 @@ pub async fn deploy_profile( ssh_command = ssh_command.arg(ssh_opt); } - ssh_command.arg(self_activate_command).spawn()?.await?; + let ssh_exit_status = ssh_command.arg(self_activate_command).status().await?; + + if !ssh_exit_status.success() { + good_panic!("Activation over SSH failed"); + } Ok(()) } diff --git a/src/utils/push.rs b/src/utils/push.rs index 9a6748e..a973572 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -22,7 +22,7 @@ pub async fn push_profile( deploy_data.profile_name, deploy_data.node_name ); - if supports_flakes { + let build_exit_status = if supports_flakes { Command::new("nix") .arg("build") .arg("--no-link") @@ -32,8 +32,8 @@ pub async fn push_profile( )) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn()? - .await?; + .status() + .await? } else { Command::new("nix-build") .arg(&repo) @@ -44,8 +44,12 @@ pub async fn push_profile( )) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn()? - .await?; + .status() + .await? + }; + + if !build_exit_status.success() { + good_panic!("`nix build` failed"); } if let Ok(local_key) = std::env::var("LOCAL_KEY") { @@ -54,7 +58,7 @@ pub async fn push_profile( deploy_data.profile_name, deploy_data.node_name ); - Command::new("nix") + let sign_exit_status = Command::new("nix") .arg("sign-paths") .arg("-r") .arg("-k") @@ -65,8 +69,12 @@ pub async fn push_profile( )?) .stdout(Stdio::null()) .stderr(Stdio::null()) - .spawn()? + .status() .await?; + + if !sign_exit_status.success() { + good_panic!("`nix sign-paths` failed"); + } } debug!( @@ -99,7 +107,7 @@ pub async fn push_profile( None => &deploy_data.node.node_settings.hostname, }; - copy_command + let copy_exit_status = copy_command .arg("--to") .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)) .arg(&deploy_data.profile.profile_settings.path) @@ -107,8 +115,12 @@ pub async fn push_profile( &deploy_defs.current_exe, )?) .env("NIX_SSHOPTS", ssh_opts_str) - .spawn()? + .status() .await?; + if !copy_exit_status.success() { + good_panic!("`nix copy` failed"); + } + Ok(()) } -- cgit v1.2.3 From 5674670a59168fb05f26e5b4fb41dd2662810e94 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 2 Oct 2020 12:58:11 -0700 Subject: General improvements, deprecate `activate` profile option in favor of executing $PROFILE/activate (Wrap It Yourself) to ensure successful rollback activations --- examples/simple/flake.nix | 47 +++++++++---- examples/system/flake.nix | 88 ++++++++++++++---------- src/activate.rs | 168 ++++++++++++++++++++++------------------------ src/utils/data.rs | 1 - src/utils/deploy.rs | 40 +++++------ src/utils/push.rs | 5 -- 6 files changed, 183 insertions(+), 166 deletions(-) diff --git a/examples/simple/flake.nix b/examples/simple/flake.nix index 800363f..f53352b 100644 --- a/examples/simple/flake.nix +++ b/examples/simple/flake.nix @@ -5,21 +5,40 @@ { description = "Deploy GNU hello to localhost"; - outputs = { self, nixpkgs }: { - deploy.nodes.example = { - hostname = "localhost"; - profiles.hello = { - user = "balsoft"; - path = nixpkgs.legacyPackages.x86_64-linux.hello; - # Just to test that it's working - activate = "$PROFILE/bin/hello"; + outputs = { self, nixpkgs }: + let + setActivate = base: activate: nixpkgs.legacyPackages.x86_64-linux.symlinkJoin { + name = ("activatable-" + base.name); + paths = [ + base + (nixpkgs.legacyPackages.x86_64-linux.writeTextFile { + name = base.name + "-activate-path"; + text = '' + #!${nixpkgs.legacyPackages.x86_64-linux.runtimeShell} + ${activate} + ''; + executable = true; + destination = "/activate"; + }) + ]; }; - }; - checks = builtins.mapAttrs (_: pkgs: { - jsonschema = pkgs.runCommandNoCC "jsonschema-deploy-simple" { } - "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${ + in + { + + deploy.nodes.example = { + hostname = "localhost"; + profiles.hello = { + user = "balsoft"; + path = setActivate nixpkgs.legacyPackages.x86_64-linux.hello "./bin/hello"; + }; + }; + 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/flake.nix b/examples/system/flake.nix index 5179258..68cf3ce 100644 --- a/examples/system/flake.nix +++ b/examples/system/flake.nix @@ -5,47 +5,65 @@ { 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"; - modules = [ ./configuration.nix ]; - }; + outputs = { self, nixpkgs }: + let + setActivate = base: activate: nixpkgs.legacyPackages.x86_64-linux.symlinkJoin { + name = ("activatable-" + base.name); + paths = [ + base + (nixpkgs.legacyPackages.x86_64-linux.writeTextFile { + name = base.name + "-activate-path"; + text = '' + #!${nixpkgs.legacyPackages.x86_64-linux.runtimeShell} + ${activate} + ''; + executable = true; + destination = "/activate"; + }) + ]; + }; + in + { + nixosConfigurations.example-nixos-system = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ./configuration.nix ]; + }; - nixosConfigurations.bare = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = - [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; - }; + nixosConfigurations.bare = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = + [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; + }; - # This is the application we actually want to run - defaultPackage.x86_64-linux = import ./hello.nix nixpkgs; + # This is the application we actually want to run + defaultPackage.x86_64-linux = import ./hello.nix nixpkgs; - deploy.nodes.example = { - sshOpts = [ "-p" "2221" ]; - hostname = "localhost"; - fastConnection = true; - profiles = { - system = { - sshUser = "admin"; - activate = "$PROFILE/bin/switch-to-configuration switch"; - path = - self.nixosConfigurations.example-nixos-system.config.system.build.toplevel; - user = "root"; - }; - hello = { - sshUser = "hello"; - activate = "$PROFILE/bin/activate"; - path = self.defaultPackage.x86_64-linux; - user = "hello"; + deploy.nodes.example = { + sshOpts = [ "-p" "2221" ]; + hostname = "localhost"; + fastConnection = true; + profiles = { + system = { + sshUser = "admin"; + path = + setActivate self.nixosConfigurations.example-nixos-system.config.system.build.toplevel "./bin/switch-to-configuration switch"; + user = "root"; + }; + hello = { + sshUser = "hello"; + path = setActivate self.defaultPackage.x86_64-linux "./bin/activate"; + user = "hello"; + }; }; }; - }; - checks = builtins.mapAttrs (_: pkgs: { - jsonschema = pkgs.runCommandNoCC "jsonschema-deploy-system" { } - "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${ + 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"; - }) nixpkgs.legacyPackages; - }; + }) + nixpkgs.legacyPackages; + }; } diff --git a/src/activate.rs b/src/activate.rs index f75303f..64baa4f 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -26,10 +26,6 @@ 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, @@ -42,21 +38,24 @@ struct Opts { pub async fn activate( profile_path: String, closure: String, - activate_cmd: Option, bootstrap_cmd: Option, auto_rollback: bool, ) -> Result<(), Box> { info!("Activating profile"); - Command::new("nix-env") + let nix_env_set_exit_status = Command::new("nix-env") .arg("-p") .arg(&profile_path) .arg("--set") .arg(&closure) .stdout(Stdio::null()) - .spawn()? + .status() .await?; + if !nix_env_set_exit_status.success() { + good_panic!("Failed to update nix-env generation"); + } + if let (Some(bootstrap_cmd), false) = (bootstrap_cmd, !Path::new(&profile_path).exists()) { let bootstrap_status = Command::new("bash") .arg("-c") @@ -76,87 +75,85 @@ pub async fn activate( } } - if let Some(activate_cmd) = activate_cmd { - let activate_status = Command::new("bash") - .arg("-c") - .arg(&activate_cmd) - .env("PROFILE", &profile_path) - .status() - .await; + let activate_status = Command::new(format!("{}/activate", profile_path)) + .env("PROFILE", &profile_path) + .status() + .await; + + 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"); + } - 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"); - } - - 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"); - } - - // TODO: Find some way to make sure this command never changes, otherwise this will not work - let re_activate_exit_status = Command::new("bash") - .arg("-c") - .arg(&activate_cmd) - .status() - .await?; - - if !re_activate_exit_status.success() { - good_panic!("Failed to re-activate the last generation"); - } - - std::process::exit(1); + 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!("{}/activate", profile_path)) + .env("PROFILE", &profile_path) + .status() + .await?; + + if !re_activate_exit_status.success() { + good_panic!("Failed to re-activate the last generation"); + } + + std::process::exit(1); } + _ => {} } Ok(()) @@ -175,7 +172,6 @@ async fn main() -> Result<(), Box> { activate( opts.profile_path, opts.closure, - opts.activate_cmd, opts.bootstrap_cmd, opts.auto_rollback, ) diff --git a/src/utils/data.rs b/src/utils/data.rs index d1dae5b..de6adfc 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -34,7 +34,6 @@ pub struct NodeSettings { #[derive(Deserialize, Debug, Clone)] pub struct ProfileSettings { pub path: String, - pub activate: Option, pub bootstrap: Option, } diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index f1f4210..7301967 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -9,10 +9,9 @@ fn build_activate_command( sudo: &Option, profile_path: &str, closure: &str, - activate_cmd: &Option, bootstrap_cmd: &Option, auto_rollback: bool, -) -> Result> { +) -> String { let mut self_activate_command = format!("{} '{}' '{}'", activate_path_str, profile_path, closure); @@ -27,18 +26,11 @@ fn build_activate_command( ); } - if let Some(ref activate_cmd) = activate_cmd { - self_activate_command = format!( - "{} --activate-cmd '{}'", - self_activate_command, activate_cmd - ); - } - if auto_rollback { self_activate_command = format!("{} --auto-rollback", self_activate_command); } - Ok(self_activate_command) + self_activate_command } #[test] @@ -47,22 +39,21 @@ fn test_activation_command_builder() { let sudo = Some("sudo -u test".to_string()); let profile_path = "/blah/profiles/test"; let closure = "/blah/etc"; - let activate_cmd = Some("$THING/bin/aaaaaaa".to_string()); let bootstrap_cmd = None; let auto_rollback = true; - match build_activate_command( - activate_path_str, - &sudo, - profile_path, - closure, - &activate_cmd, - &bootstrap_cmd, - auto_rollback, - ) { - Err(_) => panic!(""), - Ok(x) => assert_eq!(x, "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' --activate-cmd '$THING/bin/aaaaaaa' --auto-rollback".to_string()), - } + assert_eq!( + build_activate_command( + activate_path_str, + &sudo, + profile_path, + closure, + &bootstrap_cmd, + auto_rollback, + ), + "sudo -u test /blah/bin/activate '/blah/profiles/test' '/blah/etc' --auto-rollback" + .to_string(), + ); } pub async fn deploy_profile( @@ -81,10 +72,9 @@ pub async fn deploy_profile( &deploy_defs.sudo, &deploy_defs.profile_path, &deploy_data.profile.profile_settings.path, - &deploy_data.profile.profile_settings.activate, &deploy_data.profile.profile_settings.bootstrap, deploy_data.merged_settings.auto_rollback, - )?; + ); let hostname = match deploy_data.cmd_overrides.hostname { Some(ref x) => x, diff --git a/src/utils/push.rs b/src/utils/push.rs index a973572..3f48d68 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -17,11 +17,6 @@ pub async fn push_profile( deploy_data.profile_name, deploy_data.node_name ); - debug!( - "Building profile `{} for node `{}`", - deploy_data.profile_name, deploy_data.node_name - ); - let build_exit_status = if supports_flakes { Command::new("nix") .arg("build") -- cgit v1.2.3 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 --- examples/simple/flake.lock | 85 ++++++++++++++++++++++++++++++++++++- examples/simple/flake.nix | 16 +++---- examples/system/flake.lock | 85 ++++++++++++++++++++++++++++++++++++- examples/system/flake.nix | 87 ++++++++++++++------------------------ flake.nix | 25 ++++++++++- interface/README.md | 32 ++++++++++++++ interface/deploy.json | 103 +++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 9 ++-- 8 files changed, 371 insertions(+), 71 deletions(-) create mode 100644 interface/README.md create mode 100644 interface/deploy.json diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock index a57ff9d..7791c84 100644 --- a/examples/simple/flake.lock +++ b/examples/simple/flake.lock @@ -1,6 +1,73 @@ { "nodes": { + "deploy-rs": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + }, + "locked": { + "lastModified": 1601668691, + "narHash": "sha256-1mWf71DPRNgTIUKJ4Dy+CjoyZo4JkPdrwjYJy2UzqZE=", + "type": "git", + "url": "file:///home/notgne2/Dev/Serokell/deploy-rs" + }, + "original": { + "owner": "serokell", + "repo": "deploy-rs", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1597138680, + "narHash": "sha256-3pDN/W17wjVDbrkgo60xQSb24+QAPQ7ulsUq5atNni0=", + "owner": "nmattia", + "repo": "naersk", + "rev": "529e910a3f423a8211f8739290014b754b2555b6", + "type": "github" + }, + "original": { + "owner": "nmattia", + "ref": "master", + "repo": "naersk", + "type": "github" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1601091160, + "narHash": "sha256-26UI9LGjRO8Sv253zJZkoapP260QkJPQ2+vRyC1i+kI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2768436826543af2b1540e4fe6b5afa15850e155", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1600387253, + "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "72b9660dc18ba347f7cd41a9504fc181a6d87dc3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1592491430, "narHash": "sha256-7WNpr16iUyjG4caad137nCqxXNTdct202jy05lslZXA=", @@ -16,7 +83,23 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "deploy-rs": "deploy-rs", + "nixpkgs": "nixpkgs_3" + } + }, + "utils": { + "locked": { + "lastModified": 1600209923, + "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" } } }, diff --git a/examples/simple/flake.nix b/examples/simple/flake.nix index f53352b..d44b888 100644 --- a/examples/simple/flake.nix +++ b/examples/simple/flake.nix @@ -5,7 +5,9 @@ { description = "Deploy GNU hello to localhost"; - outputs = { self, nixpkgs }: + inputs.deploy-rs.url = "github:serokell/deploy-rs"; + + outputs = { self, nixpkgs, deploy-rs }: let setActivate = base: activate: nixpkgs.legacyPackages.x86_64-linux.symlinkJoin { name = ("activatable-" + base.name); @@ -29,16 +31,10 @@ hostname = "localhost"; profiles.hello = { user = "balsoft"; - path = setActivate nixpkgs.legacyPackages.x86_64-linux.hello "./bin/hello"; + path = deploy-rs.lib.x86_64-linux.setActivate nixpkgs.legacyPackages.x86_64-linux.hello "./bin/hello"; }; }; - 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; + + checks = { "x86_64-linux" = { jsonSchema = deploy-rs.lib.x86_64-linux.checkSchema self.deploy; }; }; }; } diff --git a/examples/system/flake.lock b/examples/system/flake.lock index a57ff9d..d3e489a 100644 --- a/examples/system/flake.lock +++ b/examples/system/flake.lock @@ -1,6 +1,73 @@ { "nodes": { + "deploy-rs": { + "inputs": { + "naersk": "naersk", + "nixpkgs": "nixpkgs_2", + "utils": "utils" + }, + "locked": { + "lastModified": 1601668691, + "narHash": "sha256-HvzPMsgSOQfCRoPtkwLRv09CkNjOsLHjcZtyHF+8Zbs=", + "type": "git", + "url": "file:///home/notgne2/Dev/Serokell/deploy-rs" + }, + "original": { + "owner": "serokell", + "repo": "deploy-rs", + "type": "github" + } + }, + "naersk": { + "inputs": { + "nixpkgs": "nixpkgs" + }, + "locked": { + "lastModified": 1597138680, + "narHash": "sha256-3pDN/W17wjVDbrkgo60xQSb24+QAPQ7ulsUq5atNni0=", + "owner": "nmattia", + "repo": "naersk", + "rev": "529e910a3f423a8211f8739290014b754b2555b6", + "type": "github" + }, + "original": { + "owner": "nmattia", + "ref": "master", + "repo": "naersk", + "type": "github" + } + }, "nixpkgs": { + "locked": { + "lastModified": 1601091160, + "narHash": "sha256-26UI9LGjRO8Sv253zJZkoapP260QkJPQ2+vRyC1i+kI=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "2768436826543af2b1540e4fe6b5afa15850e155", + "type": "github" + }, + "original": { + "id": "nixpkgs", + "type": "indirect" + } + }, + "nixpkgs_2": { + "locked": { + "lastModified": 1600387253, + "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "72b9660dc18ba347f7cd41a9504fc181a6d87dc3", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "nixpkgs_3": { "locked": { "lastModified": 1592491430, "narHash": "sha256-7WNpr16iUyjG4caad137nCqxXNTdct202jy05lslZXA=", @@ -16,7 +83,23 @@ }, "root": { "inputs": { - "nixpkgs": "nixpkgs" + "deploy-rs": "deploy-rs", + "nixpkgs": "nixpkgs_3" + } + }, + "utils": { + "locked": { + "lastModified": 1600209923, + "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" } } }, diff --git a/examples/system/flake.nix b/examples/system/flake.nix index 68cf3ce..32fefa1 100644 --- a/examples/system/flake.nix +++ b/examples/system/flake.nix @@ -5,65 +5,42 @@ { description = "Deploy a full system with hello service as a separate profile"; - outputs = { self, nixpkgs }: - let - setActivate = base: activate: nixpkgs.legacyPackages.x86_64-linux.symlinkJoin { - name = ("activatable-" + base.name); - paths = [ - base - (nixpkgs.legacyPackages.x86_64-linux.writeTextFile { - name = base.name + "-activate-path"; - text = '' - #!${nixpkgs.legacyPackages.x86_64-linux.runtimeShell} - ${activate} - ''; - executable = true; - destination = "/activate"; - }) - ]; - }; - in - { - nixosConfigurations.example-nixos-system = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = [ ./configuration.nix ]; - }; + inputs.deploy-rs.url = "github:serokell/deploy-rs"; - nixosConfigurations.bare = nixpkgs.lib.nixosSystem { - system = "x86_64-linux"; - modules = - [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; - }; + outputs = { self, nixpkgs, deploy-rs }: { + nixosConfigurations.example-nixos-system = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = [ ./configuration.nix ]; + }; - # This is the application we actually want to run - defaultPackage.x86_64-linux = import ./hello.nix nixpkgs; + nixosConfigurations.bare = nixpkgs.lib.nixosSystem { + system = "x86_64-linux"; + modules = + [ ./bare.nix "${nixpkgs}/nixos/modules/virtualisation/qemu-vm.nix" ]; + }; - deploy.nodes.example = { - sshOpts = [ "-p" "2221" ]; - hostname = "localhost"; - fastConnection = true; - profiles = { - system = { - sshUser = "admin"; - path = - setActivate self.nixosConfigurations.example-nixos-system.config.system.build.toplevel "./bin/switch-to-configuration switch"; - user = "root"; - }; - hello = { - sshUser = "hello"; - path = setActivate self.defaultPackage.x86_64-linux "./bin/activate"; - user = "hello"; - }; + # This is the application we actually want to run + defaultPackage.x86_64-linux = import ./hello.nix nixpkgs; + + deploy.nodes.example = { + sshOpts = [ "-p" "2221" ]; + hostname = "localhost"; + fastConnection = true; + profiles = { + system = { + sshUser = "admin"; + path = + deploy-rs.lib.x86_64-linux.setActivate self.nixosConfigurations.example-nixos-system.config.system.build.toplevel "./bin/switch-to-configuration switch"; + user = "root"; + }; + hello = { + sshUser = "hello"; + path = deploy-rs.lib.x86_64-linux.setActivate self.defaultPackage.x86_64-linux "./bin/activate"; + user = "hello"; }; }; - - 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"; - }) - nixpkgs.legacyPackages; }; + + checks = { "x86_64-linux" = { jsonSchema = deploy-rs.lib.x86_64-linux.checkSchema self.deploy; }; }; + }; } diff --git a/flake.nix b/flake.nix index d3d1817..9cfaa49 100644 --- a/flake.nix +++ b/flake.nix @@ -14,12 +14,35 @@ let pkgs = import nixpkgs { inherit system; }; naersk-lib = pkgs.callPackage naersk { }; - in { + setActivate = base: activate: pkgs.symlinkJoin { + name = ("activatable-" + base.name); + paths = [ + base + (pkgs.writeTextFile { + name = base.name + "-activate-path"; + text = '' + #!${pkgs.runtimeShell} + ${activate} + ''; + executable = true; + destination = "/activate"; + }) + ]; + }; + in + { defaultPackage = naersk-lib.buildPackage ./.; defaultApp = { type = "app"; program = "${self.defaultPackage."${system}"}/bin/deploy"; }; + + lib = { + inherit setActivate; + + checkSchema = deploy: pkgs.runCommandNoCC "jsonschema-deploy-system" { } + "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${pkgs.writeText "deploy.json" (builtins.toJSON deploy)} ${./interface/deploy.json} && touch $out"; + }; }); } 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 diff --git a/src/main.rs b/src/main.rs index 0d80e42..219c3e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ use tokio::process::Command; use merge::Merge; extern crate pretty_env_logger; + #[macro_use] extern crate log; @@ -177,12 +178,14 @@ async fn get_deployment_data( let mut c = match supports_flakes { true => Command::new("nix"), - false => Command::new("nix-instanciate"), + false => Command::new("nix-instantiate"), }; let mut build_command = match supports_flakes { true => { - c.arg("eval").arg("--json").arg(format!("{}#deploy", repo)) + c.arg("eval") + .arg("--json") + .arg(format!("{}#deploy", repo)) } false => { c @@ -190,7 +193,7 @@ async fn get_deployment_data( .arg("--read-write-mode") .arg("--json") .arg("--eval") - .arg("--E") + .arg("-E") .arg(format!("let r = import {}/.; in if builtins.isFunction r then (r {{}}).deploy else r.deploy", repo)) } }; -- cgit v1.2.3 From 58a934b997bb06f475bf707d0994f1fa37584cc1 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 5 Oct 2020 19:53:59 -0700 Subject: Clean up `simple` example --- examples/simple/flake.lock | 4 ++-- examples/simple/flake.nix | 37 +++++++++---------------------------- 2 files changed, 11 insertions(+), 30 deletions(-) diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock index 7791c84..1e2fab0 100644 --- a/examples/simple/flake.lock +++ b/examples/simple/flake.lock @@ -7,8 +7,8 @@ "utils": "utils" }, "locked": { - "lastModified": 1601668691, - "narHash": "sha256-1mWf71DPRNgTIUKJ4Dy+CjoyZo4JkPdrwjYJy2UzqZE=", + "lastModified": 1601952388, + "narHash": "sha256-g5t4JNUl4xmKfKmzo5yzligVNTBHOTgw+vkuebP4B/Q=", "type": "git", "url": "file:///home/notgne2/Dev/Serokell/deploy-rs" }, diff --git a/examples/simple/flake.nix b/examples/simple/flake.nix index d44b888..a8ad0f7 100644 --- a/examples/simple/flake.nix +++ b/examples/simple/flake.nix @@ -7,34 +7,15 @@ inputs.deploy-rs.url = "github:serokell/deploy-rs"; - outputs = { self, nixpkgs, deploy-rs }: - let - setActivate = base: activate: nixpkgs.legacyPackages.x86_64-linux.symlinkJoin { - name = ("activatable-" + base.name); - paths = [ - base - (nixpkgs.legacyPackages.x86_64-linux.writeTextFile { - name = base.name + "-activate-path"; - text = '' - #!${nixpkgs.legacyPackages.x86_64-linux.runtimeShell} - ${activate} - ''; - executable = true; - destination = "/activate"; - }) - ]; + outputs = { self, nixpkgs, deploy-rs }: { + deploy.nodes.example = { + hostname = "localhost"; + profiles.hello = { + user = "balsoft"; + path = deploy-rs.lib.x86_64-linux.setActivate nixpkgs.legacyPackages.x86_64-linux.hello "./bin/hello"; }; - in - { - - deploy.nodes.example = { - hostname = "localhost"; - profiles.hello = { - user = "balsoft"; - path = deploy-rs.lib.x86_64-linux.setActivate nixpkgs.legacyPackages.x86_64-linux.hello "./bin/hello"; - }; - }; - - checks = { "x86_64-linux" = { jsonSchema = deploy-rs.lib.x86_64-linux.checkSchema self.deploy; }; }; }; + + checks = { "x86_64-linux" = { jsonSchema = deploy-rs.lib.x86_64-linux.checkSchema self.deploy; }; }; + }; } -- cgit v1.2.3 From 1de1ad5ff893bfcabdf2bfa20d8c93a8cdbb0156 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 5 Oct 2020 19:55:01 -0700 Subject: Update GitHub URL in README --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 74b96be..15877d1 100644 --- a/README.md +++ b/README.md @@ -13,9 +13,9 @@ A Simple multi-profile Nix-flake deploy tool. ## Usage -- `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` +- `nix run github:serokell/deploy-rs your-flake#node.profile` +- `nix run github:serokell/deploy-rs your-flake#node` +- `nix run github:serokell/deploy-rs your-flake` ## API -- cgit v1.2.3 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(-) 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.2.3 From 3a92593bf9c4ca07a2b09888e4a3f7dff6c9c510 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 6 Oct 2020 11:08:40 -0700 Subject: Add skip-push flag --- examples/system/flake.lock | 6 ++--- src/main.rs | 61 +++++++++++++++++++++++++++------------------- 2 files changed, 39 insertions(+), 28 deletions(-) diff --git a/examples/system/flake.lock b/examples/system/flake.lock index 8f4ca88..b54c929 100644 --- a/examples/system/flake.lock +++ b/examples/system/flake.lock @@ -71,11 +71,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1592491430, - "narHash": "sha256-7WNpr16iUyjG4caad137nCqxXNTdct202jy05lslZXA=", + "lastModified": 1601994071, + "narHash": "sha256-J1TNrQI3Gb5Yl9b4+O6eh1Zp1ha4YhI7ewsN1M/9x68=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "07299ff81e58e16b282fe602ce5e629854dfd544", + "rev": "6f90e1df4f27326c52b805cfd01fc79a49208e5c", "type": "github" }, "original": { diff --git a/src/main.rs b/src/main.rs index fb3fb66..04d7868 100644 --- a/src/main.rs +++ b/src/main.rs @@ -51,6 +51,9 @@ 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, } #[inline] @@ -235,6 +238,7 @@ 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) { @@ -259,14 +263,16 @@ async fn run_deploy( let deploy_defs = deploy_data.defs(); - utils::push::push_profile( - supports_flakes, - check_sigs, - deploy_flake.repo, - &deploy_data, - &deploy_defs, - ) - .await?; + if !skip_push { + 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?; } @@ -276,23 +282,7 @@ async fn run_deploy( None => good_panic!("No node was found named `{}`", node_name), }; - 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 { + if !skip_push { push_all_profiles( node, node_name, @@ -305,6 +295,26 @@ 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?; @@ -363,6 +373,7 @@ async fn main() -> Result<(), Box> { data, supports_flakes, opts.checksigs, + opts.skip_push, cmd_overrides, ) .await?; -- cgit v1.2.3 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(-) 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.2.3 From 219d55e99e792a093c12dbc045bed88a4bfe1916 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Thu, 8 Oct 2020 20:01:01 -0700 Subject: Minor flake and documentation fixes --- README.md | 22 +++++++++++----------- default.nix | 13 +++++++++++++ flake.lock | 37 +++++++++++++++++++++---------------- flake.nix | 50 ++++++++++++++++++++++++++++++-------------------- 4 files changed, 75 insertions(+), 47 deletions(-) create mode 100644 default.nix diff --git a/README.md b/README.md index e622250..2710183 100644 --- a/README.md +++ b/README.md @@ -14,12 +14,12 @@ A Simple, multi-profile Nix-flake deploy tool. Basic usage: `deploy [options] `. -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`. +The given flake can be just a source `my-flake`, or optionally 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. +If you 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. @@ -27,18 +27,18 @@ Check out `deploy --help` for CLI flags! Remember to check there before making o ### 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. +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, and you can even similarly use [home-manager](https://github.com/nix-community/home-manager) on any non-privileged user. ```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"; + + # ...generic options... (see lower section) } ``` @@ -48,8 +48,6 @@ 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"; @@ -60,6 +58,8 @@ This defines a single node/server, and the profiles you intend it to run. system = {}; # Definition shown above something = {}; # Definition shown above }; + + # ...generic options... (see lower section) } ``` @@ -69,16 +69,16 @@ 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... (see lower section) } ``` -#### Generic options +### 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` @@ -96,7 +96,7 @@ A stronger definition of the schema is in the [interface directory](./interface) ## 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. +`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), designed to replace it and expand upon it. This type of design (as opposed to more traditional tools like NixOps or morph) allows for lesser-privileged deployments, and the ability to update different things independently of eachother. diff --git a/default.nix b/default.nix new file mode 100644 index 0000000..873ece4 --- /dev/null +++ b/default.nix @@ -0,0 +1,13 @@ +(import + ( + let + lock = builtins.fromJSON (builtins.readFile ./flake.lock); + in + fetchTarball { + url = "https://github.com/edolstra/flake-compat/archive/${lock.nodes.flake-compat.locked.rev}.tar.gz"; + sha256 = lock.nodes.flake-compat.locked.narHash; + } + ) + { + src = ./.; + }).defaultNix diff --git a/flake.lock b/flake.lock index 0a41d47..cbcd1bc 100644 --- a/flake.lock +++ b/flake.lock @@ -1,8 +1,26 @@ { "nodes": { + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1600853454, + "narHash": "sha256-EgsgbcJNZ9AQLVhjhfiegGjLbO+StBY9hfKsCwc8Hw8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "94cf59784c73ecec461eaa291918eff0bfb538ac", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "naersk": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1597138680, @@ -20,20 +38,6 @@ } }, "nixpkgs": { - "locked": { - "lastModified": 1601091160, - "narHash": "sha256-26UI9LGjRO8Sv253zJZkoapP260QkJPQ2+vRyC1i+kI=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2768436826543af2b1540e4fe6b5afa15850e155", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_2": { "locked": { "lastModified": 1600387253, "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", @@ -51,8 +55,9 @@ }, "root": { "inputs": { + "flake-compat": "flake-compat", "naersk": "naersk", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs", "utils": "utils" } }, diff --git a/flake.nix b/flake.nix index 9cfaa49..81603e4 100644 --- a/flake.nix +++ b/flake.nix @@ -3,43 +3,53 @@ # SPDX-License-Identifier: MPL-2.0 { + description = "A Simple multi-profile Nix-flake deploy tool."; + inputs = { - naersk.url = "github:nmattia/naersk/master"; nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; + naersk = { + url = "github:nmattia/naersk/master"; + inputs.nixpkgs.follows = "nixpkgs"; + }; utils.url = "github:numtide/flake-utils"; + flake-compat = { + url = "github:edolstra/flake-compat"; + flake = false; + }; }; - outputs = { self, nixpkgs, utils, naersk }: + outputs = { self, nixpkgs, utils, naersk, ... }: utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { inherit system; }; naersk-lib = pkgs.callPackage naersk { }; - setActivate = base: activate: pkgs.symlinkJoin { - name = ("activatable-" + base.name); - paths = [ - base - (pkgs.writeTextFile { - name = base.name + "-activate-path"; - text = '' - #!${pkgs.runtimeShell} - ${activate} - ''; - executable = true; - destination = "/activate"; - }) - ]; - }; in { - defaultPackage = naersk-lib.buildPackage ./.; + defaultPackage = self.packages."${system}".deploy-rs; + packages.deploy-rs = naersk-lib.buildPackage ./.; - defaultApp = { + defaultApp = self.apps."${system}".deploy-rs; + apps.deploy-rs = { type = "app"; program = "${self.defaultPackage."${system}"}/bin/deploy"; }; lib = { - inherit setActivate; + setActivate = base: activate: pkgs.symlinkJoin { + name = ("activatable-" + base.name); + paths = [ + base + (pkgs.writeTextFile { + name = base.name + "-activate-path"; + text = '' + #!${pkgs.runtimeShell} + ${activate} + ''; + executable = true; + destination = "/activate"; + }) + ]; + }; checkSchema = deploy: pkgs.runCommandNoCC "jsonschema-deploy-system" { } "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${pkgs.writeText "deploy.json" (builtins.toJSON deploy)} ${./interface/deploy.json} && touch $out"; -- cgit v1.2.3 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(-) 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.2.3 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(+) 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.2.3 From e463c62922ad09f016e4f1dd1d6d0cabccb0ff79 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 11 Oct 2020 15:08:02 -0700 Subject: Move activate script location, use buildEnv for setActivate --- flake.nix | 4 ++-- src/activate.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/flake.nix b/flake.nix index 81603e4..73aa802 100644 --- a/flake.nix +++ b/flake.nix @@ -35,7 +35,7 @@ }; lib = { - setActivate = base: activate: pkgs.symlinkJoin { + setActivate = base: activate: pkgs.buildEnv { name = ("activatable-" + base.name); paths = [ base @@ -46,7 +46,7 @@ ${activate} ''; executable = true; - destination = "/activate"; + destination = "/deploy-rs-activate"; }) ]; }; diff --git a/src/activate.rs b/src/activate.rs index 64baa4f..38fc832 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -75,7 +75,7 @@ pub async fn activate( } } - let activate_status = Command::new(format!("{}/activate", profile_path)) + let activate_status = Command::new(format!("{}/deploy-rs-activate", profile_path)) .env("PROFILE", &profile_path) .status() .await; @@ -142,7 +142,7 @@ pub async fn activate( info!("Attempting re-activate last generation"); - let re_activate_exit_status = Command::new(format!("{}/activate", profile_path)) + let re_activate_exit_status = Command::new(format!("{}/deploy-rs-activate", profile_path)) .env("PROFILE", &profile_path) .status() .await?; -- cgit v1.2.3 From f2e5b9b39303ec66142caadad6aea74ee56a4c93 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 11 Oct 2020 15:09:37 -0700 Subject: Update lock files --- examples/simple/flake.lock | 53 +++++++++++++++++++++++++--------------------- examples/system/flake.lock | 53 +++++++++++++++++++++++++--------------------- flake.lock | 18 ++++++++-------- 3 files changed, 67 insertions(+), 57 deletions(-) diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock index 8f4ca88..ff339a8 100644 --- a/examples/simple/flake.lock +++ b/examples/simple/flake.lock @@ -2,16 +2,17 @@ "nodes": { "deploy-rs": { "inputs": { + "flake-compat": "flake-compat", "naersk": "naersk", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs", "utils": "utils" }, "locked": { - "lastModified": 1601952901, - "narHash": "sha256-6U0JIlh6GLqkxdyUiVRbph9k1lVCtWLno2uM/Fd/ZzI=", + "lastModified": 1602454082, + "narHash": "sha256-nx6ULJCys3u+nLFHrt9+zux3fsfTloLTRd/FpJcL//Q=", "owner": "serokell", "repo": "deploy-rs", - "rev": "1de1ad5ff893bfcabdf2bfa20d8c93a8cdbb0156", + "rev": "e463c62922ad09f016e4f1dd1d6d0cabccb0ff79", "type": "github" }, "original": { @@ -20,9 +21,27 @@ "type": "github" } }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1600853454, + "narHash": "sha256-EgsgbcJNZ9AQLVhjhfiegGjLbO+StBY9hfKsCwc8Hw8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "94cf59784c73ecec461eaa291918eff0bfb538ac", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "naersk": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1597138680, @@ -40,20 +59,6 @@ } }, "nixpkgs": { - "locked": { - "lastModified": 1601091160, - "narHash": "sha256-26UI9LGjRO8Sv253zJZkoapP260QkJPQ2+vRyC1i+kI=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2768436826543af2b1540e4fe6b5afa15850e155", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_2": { "locked": { "lastModified": 1600387253, "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", @@ -69,13 +74,13 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_2": { "locked": { - "lastModified": 1592491430, - "narHash": "sha256-7WNpr16iUyjG4caad137nCqxXNTdct202jy05lslZXA=", + "lastModified": 1602453019, + "narHash": "sha256-bEzkPMG5JTFpd7xsl73/K/hZ9jBgiRKMdkpmwAG4lwI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "07299ff81e58e16b282fe602ce5e629854dfd544", + "rev": "a6fec75d0472670448b9708e1619fef2c36af9d5", "type": "github" }, "original": { @@ -86,7 +91,7 @@ "root": { "inputs": { "deploy-rs": "deploy-rs", - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_2" } }, "utils": { diff --git a/examples/system/flake.lock b/examples/system/flake.lock index b54c929..ff339a8 100644 --- a/examples/system/flake.lock +++ b/examples/system/flake.lock @@ -2,16 +2,17 @@ "nodes": { "deploy-rs": { "inputs": { + "flake-compat": "flake-compat", "naersk": "naersk", - "nixpkgs": "nixpkgs_2", + "nixpkgs": "nixpkgs", "utils": "utils" }, "locked": { - "lastModified": 1601952901, - "narHash": "sha256-6U0JIlh6GLqkxdyUiVRbph9k1lVCtWLno2uM/Fd/ZzI=", + "lastModified": 1602454082, + "narHash": "sha256-nx6ULJCys3u+nLFHrt9+zux3fsfTloLTRd/FpJcL//Q=", "owner": "serokell", "repo": "deploy-rs", - "rev": "1de1ad5ff893bfcabdf2bfa20d8c93a8cdbb0156", + "rev": "e463c62922ad09f016e4f1dd1d6d0cabccb0ff79", "type": "github" }, "original": { @@ -20,9 +21,27 @@ "type": "github" } }, + "flake-compat": { + "flake": false, + "locked": { + "lastModified": 1600853454, + "narHash": "sha256-EgsgbcJNZ9AQLVhjhfiegGjLbO+StBY9hfKsCwc8Hw8=", + "owner": "edolstra", + "repo": "flake-compat", + "rev": "94cf59784c73ecec461eaa291918eff0bfb538ac", + "type": "github" + }, + "original": { + "owner": "edolstra", + "repo": "flake-compat", + "type": "github" + } + }, "naersk": { "inputs": { - "nixpkgs": "nixpkgs" + "nixpkgs": [ + "nixpkgs" + ] }, "locked": { "lastModified": 1597138680, @@ -40,20 +59,6 @@ } }, "nixpkgs": { - "locked": { - "lastModified": 1601091160, - "narHash": "sha256-26UI9LGjRO8Sv253zJZkoapP260QkJPQ2+vRyC1i+kI=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "2768436826543af2b1540e4fe6b5afa15850e155", - "type": "github" - }, - "original": { - "id": "nixpkgs", - "type": "indirect" - } - }, - "nixpkgs_2": { "locked": { "lastModified": 1600387253, "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", @@ -69,13 +74,13 @@ "type": "github" } }, - "nixpkgs_3": { + "nixpkgs_2": { "locked": { - "lastModified": 1601994071, - "narHash": "sha256-J1TNrQI3Gb5Yl9b4+O6eh1Zp1ha4YhI7ewsN1M/9x68=", + "lastModified": 1602453019, + "narHash": "sha256-bEzkPMG5JTFpd7xsl73/K/hZ9jBgiRKMdkpmwAG4lwI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "6f90e1df4f27326c52b805cfd01fc79a49208e5c", + "rev": "a6fec75d0472670448b9708e1619fef2c36af9d5", "type": "github" }, "original": { @@ -86,7 +91,7 @@ "root": { "inputs": { "deploy-rs": "deploy-rs", - "nixpkgs": "nixpkgs_3" + "nixpkgs": "nixpkgs_2" } }, "utils": { diff --git a/flake.lock b/flake.lock index cbcd1bc..c9e4ff3 100644 --- a/flake.lock +++ b/flake.lock @@ -23,11 +23,11 @@ ] }, "locked": { - "lastModified": 1597138680, - "narHash": "sha256-3pDN/W17wjVDbrkgo60xQSb24+QAPQ7ulsUq5atNni0=", + "lastModified": 1602173141, + "narHash": "sha256-m6wU6lP0wf2OMw3KtJqn27ITtg29+ftciGHicLiVSGE=", "owner": "nmattia", "repo": "naersk", - "rev": "529e910a3f423a8211f8739290014b754b2555b6", + "rev": "22b96210b2433228d42bce460f3befbdcfde7520", "type": "github" }, "original": { @@ -39,11 +39,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1600387253, - "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", + "lastModified": 1601961544, + "narHash": "sha256-uuh9CkDWkXlXse8IcergqoIM5JffqfQDKsl1uHB7XJI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "72b9660dc18ba347f7cd41a9504fc181a6d87dc3", + "rev": "89281dd1dfed6839610f0ccad0c0e493606168fe", "type": "github" }, "original": { @@ -63,11 +63,11 @@ }, "utils": { "locked": { - "lastModified": 1600209923, - "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "lastModified": 1601282935, + "narHash": "sha256-WQAFV6sGGQxrRs3a+/Yj9xUYvhTpukQJIcMbIi7LCJ4=", "owner": "numtide", "repo": "flake-utils", - "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "rev": "588973065fce51f4763287f0fda87a174d78bf48", "type": "github" }, "original": { -- cgit v1.2.3 From e5bd558c5b6505621d3b5a27e9b39bf54f6788a1 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 11 Oct 2020 15:19:09 -0700 Subject: Set working directory during activation to the profile path --- src/activate.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/activate.rs b/src/activate.rs index 38fc832..0b7d28d 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -77,6 +77,7 @@ pub async fn activate( let activate_status = Command::new(format!("{}/deploy-rs-activate", profile_path)) .env("PROFILE", &profile_path) + .current_dir(&profile_path) .status() .await; @@ -144,6 +145,7 @@ pub async fn activate( let re_activate_exit_status = Command::new(format!("{}/deploy-rs-activate", profile_path)) .env("PROFILE", &profile_path) + .current_dir(&profile_path) .status() .await?; -- cgit v1.2.3 From 898bb2814b51fb76ef3d54164768512ed13016be Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 11 Oct 2020 15:19:34 -0700 Subject: Update lockfiles --- examples/simple/flake.lock | 24 ++++++++++++------------ examples/system/flake.lock | 24 ++++++++++++------------ 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock index ff339a8..a6a3fa9 100644 --- a/examples/simple/flake.lock +++ b/examples/simple/flake.lock @@ -8,11 +8,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1602454082, - "narHash": "sha256-nx6ULJCys3u+nLFHrt9+zux3fsfTloLTRd/FpJcL//Q=", + "lastModified": 1602454749, + "narHash": "sha256-wSPZEl6xyqewPrNQg9k6z3DLshn2Fwha8mDv8ECWMf8=", "owner": "serokell", "repo": "deploy-rs", - "rev": "e463c62922ad09f016e4f1dd1d6d0cabccb0ff79", + "rev": "e5bd558c5b6505621d3b5a27e9b39bf54f6788a1", "type": "github" }, "original": { @@ -44,11 +44,11 @@ ] }, "locked": { - "lastModified": 1597138680, - "narHash": "sha256-3pDN/W17wjVDbrkgo60xQSb24+QAPQ7ulsUq5atNni0=", + "lastModified": 1602173141, + "narHash": "sha256-m6wU6lP0wf2OMw3KtJqn27ITtg29+ftciGHicLiVSGE=", "owner": "nmattia", "repo": "naersk", - "rev": "529e910a3f423a8211f8739290014b754b2555b6", + "rev": "22b96210b2433228d42bce460f3befbdcfde7520", "type": "github" }, "original": { @@ -60,11 +60,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1600387253, - "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", + "lastModified": 1601961544, + "narHash": "sha256-uuh9CkDWkXlXse8IcergqoIM5JffqfQDKsl1uHB7XJI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "72b9660dc18ba347f7cd41a9504fc181a6d87dc3", + "rev": "89281dd1dfed6839610f0ccad0c0e493606168fe", "type": "github" }, "original": { @@ -96,11 +96,11 @@ }, "utils": { "locked": { - "lastModified": 1600209923, - "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "lastModified": 1601282935, + "narHash": "sha256-WQAFV6sGGQxrRs3a+/Yj9xUYvhTpukQJIcMbIi7LCJ4=", "owner": "numtide", "repo": "flake-utils", - "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "rev": "588973065fce51f4763287f0fda87a174d78bf48", "type": "github" }, "original": { diff --git a/examples/system/flake.lock b/examples/system/flake.lock index ff339a8..a6a3fa9 100644 --- a/examples/system/flake.lock +++ b/examples/system/flake.lock @@ -8,11 +8,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1602454082, - "narHash": "sha256-nx6ULJCys3u+nLFHrt9+zux3fsfTloLTRd/FpJcL//Q=", + "lastModified": 1602454749, + "narHash": "sha256-wSPZEl6xyqewPrNQg9k6z3DLshn2Fwha8mDv8ECWMf8=", "owner": "serokell", "repo": "deploy-rs", - "rev": "e463c62922ad09f016e4f1dd1d6d0cabccb0ff79", + "rev": "e5bd558c5b6505621d3b5a27e9b39bf54f6788a1", "type": "github" }, "original": { @@ -44,11 +44,11 @@ ] }, "locked": { - "lastModified": 1597138680, - "narHash": "sha256-3pDN/W17wjVDbrkgo60xQSb24+QAPQ7ulsUq5atNni0=", + "lastModified": 1602173141, + "narHash": "sha256-m6wU6lP0wf2OMw3KtJqn27ITtg29+ftciGHicLiVSGE=", "owner": "nmattia", "repo": "naersk", - "rev": "529e910a3f423a8211f8739290014b754b2555b6", + "rev": "22b96210b2433228d42bce460f3befbdcfde7520", "type": "github" }, "original": { @@ -60,11 +60,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1600387253, - "narHash": "sha256-WtdpHuiunPF9QMlcXrWJkESuIjSSjP9WMOKvYQS/D7M=", + "lastModified": 1601961544, + "narHash": "sha256-uuh9CkDWkXlXse8IcergqoIM5JffqfQDKsl1uHB7XJI=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "72b9660dc18ba347f7cd41a9504fc181a6d87dc3", + "rev": "89281dd1dfed6839610f0ccad0c0e493606168fe", "type": "github" }, "original": { @@ -96,11 +96,11 @@ }, "utils": { "locked": { - "lastModified": 1600209923, - "narHash": "sha256-zoOWauTliFEjI++esk6Jzk7QO5EKpddWXQm9yQK24iM=", + "lastModified": 1601282935, + "narHash": "sha256-WQAFV6sGGQxrRs3a+/Yj9xUYvhTpukQJIcMbIi7LCJ4=", "owner": "numtide", "repo": "flake-utils", - "rev": "3cd06d3c1df6879c9e41cb2c33113df10566c760", + "rev": "588973065fce51f4763287f0fda87a174d78bf48", "type": "github" }, "original": { -- cgit v1.2.3 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 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.2.3 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(-) 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.2.3 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(-) 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.2.3 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(-) 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.2.3 From 75449025beb621f2219b99b52b1287af3a6d1676 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Tue, 13 Oct 2020 19:10:58 -0700 Subject: Mark magic rollback off the checklist --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cbc312d..edaf144 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ This type of design (as opposed to more traditional tools like NixOps or morph) - UI (?) - automatic kexec lustration of servers (maybe) - Remote health checks -- Rollback on reconnection failure (technically, rollback if not reconnected to) +- ~~Rollback on reconnection failure (technically, rollback if not reconnected to)~~ ## About Serokell -- cgit v1.2.3 From 32cd88fdc00c7258a21314a3fcec29d14a624280 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 15:05:56 -0700 Subject: Add note about `activate` in README --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index edaf144..009cd7f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,8 @@ If you require a signing key to push closures to your server, specify the path t Check out `deploy --help` for CLI flags! Remember to check there before making one-time changes to things like hostnames. +There is also an `activate` binary though this should be ignored, it is only used internally and for testing/hacking purposes. + ## API ### Profile -- cgit v1.2.3 From 48d1e48429d72b50c72282e47a68be130ea506ad Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 15:06:14 -0700 Subject: Remove redundant default --- src/utils/data.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/data.rs b/src/utils/data.rs index 5c58e3b..6cf2c5a 100644 --- a/src/utils/data.rs +++ b/src/utils/data.rs @@ -18,9 +18,9 @@ pub struct GenericSettings { )] #[merge(strategy = merge::vec::append)] pub ssh_opts: Vec, - #[serde(rename(deserialize = "fastConnection"), default)] + #[serde(rename(deserialize = "fastConnection"))] pub fast_connection: Option, - #[serde(rename(deserialize = "autoRollback"), default)] + #[serde(rename(deserialize = "autoRollback"))] pub auto_rollback: Option, #[serde(rename(deserialize = "confirmTimeout"))] pub confirm_timeout: Option, -- cgit v1.2.3 From 47e94f8dfd2ec9c6aacc6ba8d74f629e70433567 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 21:54:13 -0700 Subject: Warn when flakes are not available, forward stderr of Nix evaluation --- src/main.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index cedf684..5d76e3d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -221,11 +221,11 @@ async fn get_deployment_data( build_command = build_command.arg(extra_arg); } - let build_output = build_command - // .stdout(Stdio::null()) - // .stderr(Stdio::null()) - .output() - .await?; + let build_child = build_command + .stdout(Stdio::piped()) + .spawn()?; + + let build_output = build_child.wait_with_output().await?; if !build_output.status.success() { good_panic!( @@ -353,6 +353,10 @@ async fn main() -> Result<(), Box> { let supports_flakes = test_flake_support().await?; + if !supports_flakes { + warn!("A Nix version without flakes support was detected, support for this is work in progress"); + } + let data = get_deployment_data(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await?; -- cgit v1.2.3 From c55471f1a52fc7cb4c467a3c9718640cdb950a22 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 22:03:15 -0700 Subject: Fix log messages, prevent non-flake builds writing to result, unmute stderr on nix builds --- src/utils/push.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/utils/push.rs b/src/utils/push.rs index f80f9f8..a82c9d4 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -13,7 +13,7 @@ pub async fn push_profile( deploy_defs: &super::DeployDefs<'_>, ) -> Result<(), Box> { info!( - "Pushing profile `{}` for node `{}`", + "Building profile `{}` for node `{}`", deploy_data.profile_name, deploy_data.node_name ); @@ -26,19 +26,18 @@ pub async fn push_profile( repo, deploy_data.node_name, deploy_data.profile_name )) .stdout(Stdio::null()) - .stderr(Stdio::null()) .status() .await? } else { Command::new("nix-build") .arg(&repo) + .arg("--no-out-link") .arg("-A") .arg(format!( "deploy.nodes.{}.profiles.{}.path", deploy_data.node_name, deploy_data.profile_name )) .stdout(Stdio::null()) - .stderr(Stdio::null()) .status() .await? }; @@ -73,7 +72,7 @@ pub async fn push_profile( } debug!( - "Copying profile `{} for node `{}`", + "Copying profile `{}` to node `{}`", deploy_data.profile_name, deploy_data.node_name ); -- cgit v1.2.3 From 4084e0516d3903acd80ca465f9906e5d3bce2659 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 22:14:14 -0700 Subject: Enable color for activation command --- src/utils/deploy.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 9b2f685..1ef7f11 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -112,7 +112,9 @@ pub async fn deploy_profile( }; let mut c = Command::new("ssh"); - let mut ssh_command = c.arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); + let mut ssh_command = c + .arg("-t") + .arg(format!("ssh://{}@{}", deploy_defs.ssh_user, hostname)); for ssh_opt in &deploy_data.merged_settings.ssh_opts { ssh_command = ssh_command.arg(ssh_opt); -- cgit v1.2.3 From 72b066b293befec048f6a1b2f8d7a4b103ae4edf Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 22:44:52 -0700 Subject: Add an option to keep build results --- src/main.rs | 27 ++++++++++++++++++++++++--- src/utils/push.rs | 43 ++++++++++++++++++++++++++++++------------- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5d76e3d..1358a83 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,6 +33,13 @@ struct Opts { /// Extra arguments to be passed to nix build extra_build_args: Vec, + /// Keep the build outputs of each built profile + #[clap(short, long)] + keep_result: bool, + /// Location to keep outputs from built profiles in + #[clap(short, long)] + result_path: Option, + /// Override the SSH user with the given value #[clap(long)] ssh_user: Option, @@ -71,6 +78,8 @@ async fn push_all_profiles( top_settings: &utils::data::GenericSettings, check_sigs: bool, cmd_overrides: &utils::CmdOverrides, + keep_result: bool, + result_path: Option<&str>, ) -> Result<(), Box> { info!("Pushing all profiles for `{}`", node_name); @@ -115,6 +124,8 @@ async fn push_all_profiles( repo, &deploy_data, &deploy_defs, + keep_result, + result_path, ) .await?; } @@ -221,9 +232,7 @@ async fn get_deployment_data( build_command = build_command.arg(extra_arg); } - let build_child = build_command - .stdout(Stdio::piped()) - .spawn()?; + let build_child = build_command.stdout(Stdio::piped()).spawn()?; let build_output = build_child.wait_with_output().await?; @@ -245,6 +254,8 @@ async fn run_deploy( supports_flakes: bool, check_sigs: bool, cmd_overrides: utils::CmdOverrides, + keep_result: bool, + result_path: Option<&str>, ) -> Result<(), Box> { match (deploy_flake.node, deploy_flake.profile) { (Some(node_name), Some(profile_name)) => { @@ -274,6 +285,8 @@ async fn run_deploy( deploy_flake.repo, &deploy_data, &deploy_defs, + keep_result, + result_path, ) .await?; @@ -293,6 +306,8 @@ async fn run_deploy( &data.generic_settings, check_sigs, &cmd_overrides, + keep_result, + result_path, ) .await?; @@ -310,6 +325,8 @@ async fn run_deploy( &data.generic_settings, check_sigs, &cmd_overrides, + keep_result, + result_path, ) .await?; } @@ -360,12 +377,16 @@ async fn main() -> Result<(), Box> { let data = get_deployment_data(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await?; + let result_path = opts.result_path.as_deref(); + run_deploy( deploy_flake, data, supports_flakes, opts.checksigs, cmd_overrides, + opts.keep_result, + result_path, ) .await?; diff --git a/src/utils/push.rs b/src/utils/push.rs index a82c9d4..5e87d5c 100644 --- a/src/utils/push.rs +++ b/src/utils/push.rs @@ -11,25 +11,27 @@ pub async fn push_profile( repo: &str, deploy_data: &super::DeployData<'_>, deploy_defs: &super::DeployDefs<'_>, + keep_result: bool, + result_path: Option<&str>, ) -> Result<(), Box> { info!( "Building profile `{}` for node `{}`", deploy_data.profile_name, deploy_data.node_name ); - let build_exit_status = if supports_flakes { + let mut build_c = if supports_flakes { Command::new("nix") - .arg("build") - .arg("--no-link") - .arg(format!( - "{}#deploy.nodes.{}.profiles.{}.path", - repo, deploy_data.node_name, deploy_data.profile_name - )) - .stdout(Stdio::null()) - .status() - .await? } else { Command::new("nix-build") + }; + + let mut build_command = if supports_flakes { + build_c.arg("build").arg("--no-link").arg(format!( + "{}#deploy.nodes.{}.profiles.{}.path", + repo, deploy_data.node_name, deploy_data.profile_name + )) + } else { + build_c .arg(&repo) .arg("--no-out-link") .arg("-A") @@ -37,11 +39,26 @@ pub async fn push_profile( "deploy.nodes.{}.profiles.{}.path", deploy_data.node_name, deploy_data.profile_name )) - .stdout(Stdio::null()) - .status() - .await? }; + build_command = match (keep_result, supports_flakes) { + (true, _) => { + let result_path = match result_path { + Some(x) => x, + None => "./.deploy-gc", + }; + + build_command.arg("--out-link").arg(format!( + "{}/{}/{}", + result_path, deploy_data.node_name, deploy_data.profile_name + )) + } + (false, false) => build_command.arg("--no-out-link"), + (false, true) => build_command.arg("--no-link"), + }; + + let build_exit_status = build_command.stdout(Stdio::null()).status().await?; + if !build_exit_status.success() { good_panic!("`nix build` failed"); } -- cgit v1.2.3 From 73d14d456f288b98b9e0874d74a5a48d8a86e2b7 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Fri, 23 Oct 2020 22:45:05 -0700 Subject: Add TODO for remote building --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 009cd7f..f5a6458 100644 --- a/README.md +++ b/README.md @@ -113,6 +113,7 @@ This type of design (as opposed to more traditional tools like NixOps or morph) - automatic kexec lustration of servers (maybe) - Remote health checks - ~~Rollback on reconnection failure (technically, rollback if not reconnected to)~~ +- Optionally build on remote server ## About Serokell -- cgit v1.2.3 From 155f4bf45204b3090cc07271d2648ac7c024bbd1 Mon Sep 17 00:00:00 2001 From: notgne2 Date: Sun, 25 Oct 2020 13:15:35 -0700 Subject: Fail correctly if initial activation fails --- src/activate.rs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/activate.rs b/src/activate.rs index 55ceb27..4fdb59c 100644 --- a/src/activate.rs +++ b/src/activate.rs @@ -114,7 +114,7 @@ pub async fn deactivate(profile_path: &str) -> Result<(), Box (), - _ if auto_rollback => return Ok(deactivate(&profile_path).await?), - _ => (), - } + let activate_status_all = match activate_status { + Ok(s) if s.success() => Ok(()), + Ok(_) => Err(std::io::Error::new(std::io::ErrorKind::Other, "Activation did not succeed")), + Err(x) => Err(x), + }; + + deactivate_on_err(&profile_path, activate_status_all).await; info!("Activation succeeded!"); -- cgit v1.2.3 From 8b3b91344abe6b6361b22943a0176e41fb2d585c Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 26 Oct 2020 11:29:01 -0700 Subject: Improve checks usage, add check for activation script existing --- flake.nix | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/flake.nix b/flake.nix index 73aa802..fe24754 100644 --- a/flake.nix +++ b/flake.nix @@ -34,7 +34,7 @@ program = "${self.defaultPackage."${system}"}/bin/deploy"; }; - lib = { + lib = rec { setActivate = base: activate: pkgs.buildEnv { name = ("activatable-" + base.name); paths = [ @@ -51,8 +51,26 @@ ]; }; - checkSchema = deploy: pkgs.runCommandNoCC "jsonschema-deploy-system" { } - "${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${pkgs.writeText "deploy.json" (builtins.toJSON deploy)} ${./interface/deploy.json} && touch $out"; + # DEPRECATED + checkSchema = checks.schema; + + deployChecks = deploy: builtins.mapAttrs (_: check: check deploy) checks; + + checks = { + schema = deploy: pkgs.runCommandNoCC "jsonschema-deploy-system" { } '' + ${pkgs.python3.pkgs.jsonschema}/bin/jsonschema -i ${pkgs.writeText "deploy.json" (builtins.toJSON deploy)} ${./interface/deploy.json} && touch $out + ''; + + activate = deploy: + let + allPaths = pkgs.lib.flatten (pkgs.lib.mapAttrsToList (nodeName: node: pkgs.lib.mapAttrsToList (profileName: profile: profile.path) node.profiles) deploy.nodes); + in + pkgs.runCommandNoCC "deploy-rs-check-activate" { } '' + for i in ${builtins.concatStringsSep " " allPaths}; do test -f "$i/deploy-rs-activate" || (echo "A profile path is missing an activation script" && exit 1); done + + touch $out + ''; + }; }; }); } -- cgit v1.2.3 From 426fb3c489dcbb4ccbf98a3ab6a7fe25e71b95ca Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 26 Oct 2020 12:24:57 -0700 Subject: Automatically run checks when deploying --- src/main.rs | 44 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 1358a83..48fb482 100644 --- a/src/main.rs +++ b/src/main.rs @@ -40,6 +40,10 @@ struct Opts { #[clap(short, long)] result_path: Option, + /// Skip the automatic pre-build checks + #[clap(short, long)] + skip_checks: bool, + /// Override the SSH user with the given value #[clap(long)] ssh_user: Option, @@ -197,8 +201,42 @@ async fn test_flake_support() -> Result> { .success()) } +async fn check_deployment(supports_flakes: bool, repo: &str, extra_build_args: &[String]) -> () { + let mut c = match supports_flakes { + true => Command::new("nix"), + false => Command::new("nix-build"), + }; + + let mut check_command = match supports_flakes { + true => { + c.arg("flake") + .arg("check") + .arg(repo) + } + false => { + c.arg("-E") + .arg("--no-out-link") + .arg(format!("let r = import {}/.; in (if builtins.isFunction r then (r {{}}) else r).checks.${{builtins.currentSystem}}", repo)) + } + }; + + for extra_arg in extra_build_args { + check_command = check_command.arg(extra_arg); + } + + let check_status = match check_command.status().await { + Ok(x) => x, + Err(err) => good_panic!("Error running checks for the given flake repo: {:?}", err), + }; + + if !check_status.success() { + good_panic!("Checks failed for the given flake repo"); + } + + () +} + /// Evaluates the Nix in the given `repo` and return the processed Data from it -#[inline] async fn get_deployment_data( supports_flakes: bool, repo: &str, @@ -374,6 +412,10 @@ async fn main() -> Result<(), Box> { warn!("A Nix version without flakes support was detected, support for this is work in progress"); } + if !opts.skip_checks { + check_deployment(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await; + } + let data = get_deployment_data(supports_flakes, deploy_flake.repo, &opts.extra_build_args).await?; -- cgit v1.2.3 From 7ec0bc21cd3678e39270d7ea59bccaefa288abfa Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 26 Oct 2020 12:27:20 -0700 Subject: Update example flakes --- examples/simple/flake.lock | 12 ++++++------ examples/simple/flake.nix | 2 +- examples/system/flake.lock | 12 ++++++------ examples/system/flake.nix | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/examples/simple/flake.lock b/examples/simple/flake.lock index a6a3fa9..fc92f40 100644 --- a/examples/simple/flake.lock +++ b/examples/simple/flake.lock @@ -8,11 +8,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1602454749, - "narHash": "sha256-wSPZEl6xyqewPrNQg9k6z3DLshn2Fwha8mDv8ECWMf8=", + "lastModified": 1603740297, + "narHash": "sha256-yeTrA8AaLzDFICApX725gQhKoHNI2TCqWAeOl9axVZE=", "owner": "serokell", "repo": "deploy-rs", - "rev": "e5bd558c5b6505621d3b5a27e9b39bf54f6788a1", + "rev": "426fb3c489dcbb4ccbf98a3ab6a7fe25e71b95ca", "type": "github" }, "original": { @@ -76,11 +76,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1602453019, - "narHash": "sha256-bEzkPMG5JTFpd7xsl73/K/hZ9jBgiRKMdkpmwAG4lwI=", + "lastModified": 1603739127, + "narHash": "sha256-mdLESpo4jXrAynLp7ypRaqkx6IS1jx2l78f1tg9iiJU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a6fec75d0472670448b9708e1619fef2c36af9d5", + "rev": "d699505277b99e4698d90563c5eb1b62ba5ba0ea", "type": "github" }, "original": { diff --git a/examples/simple/flake.nix b/examples/simple/flake.nix index a8ad0f7..e5003c7 100644 --- a/examples/simple/flake.nix +++ b/examples/simple/flake.nix @@ -16,6 +16,6 @@ }; }; - checks = { "x86_64-linux" = { jsonSchema = deploy-rs.lib.x86_64-linux.checkSchema self.deploy; }; }; + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib; }; } diff --git a/examples/system/flake.lock b/examples/system/flake.lock index a6a3fa9..fc92f40 100644 --- a/examples/system/flake.lock +++ b/examples/system/flake.lock @@ -8,11 +8,11 @@ "utils": "utils" }, "locked": { - "lastModified": 1602454749, - "narHash": "sha256-wSPZEl6xyqewPrNQg9k6z3DLshn2Fwha8mDv8ECWMf8=", + "lastModified": 1603740297, + "narHash": "sha256-yeTrA8AaLzDFICApX725gQhKoHNI2TCqWAeOl9axVZE=", "owner": "serokell", "repo": "deploy-rs", - "rev": "e5bd558c5b6505621d3b5a27e9b39bf54f6788a1", + "rev": "426fb3c489dcbb4ccbf98a3ab6a7fe25e71b95ca", "type": "github" }, "original": { @@ -76,11 +76,11 @@ }, "nixpkgs_2": { "locked": { - "lastModified": 1602453019, - "narHash": "sha256-bEzkPMG5JTFpd7xsl73/K/hZ9jBgiRKMdkpmwAG4lwI=", + "lastModified": 1603739127, + "narHash": "sha256-mdLESpo4jXrAynLp7ypRaqkx6IS1jx2l78f1tg9iiJU=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "a6fec75d0472670448b9708e1619fef2c36af9d5", + "rev": "d699505277b99e4698d90563c5eb1b62ba5ba0ea", "type": "github" }, "original": { diff --git a/examples/system/flake.nix b/examples/system/flake.nix index 32fefa1..021f9db 100644 --- a/examples/system/flake.nix +++ b/examples/system/flake.nix @@ -41,6 +41,6 @@ }; }; - checks = { "x86_64-linux" = { jsonSchema = deploy-rs.lib.x86_64-linux.checkSchema self.deploy; }; }; + checks = builtins.mapAttrs (system: deployLib: deployLib.deployChecks self.deploy) deploy-rs.lib; }; } -- cgit v1.2.3 From df002c31a64409350a3cb8825364542c65a4d00a Mon Sep 17 00:00:00 2001 From: notgne2 Date: Mon, 26 Oct 2020 12:44:19 -0700 Subject: Add more debug logs --- src/main.rs | 5 ++++- src/utils/deploy.rs | 7 +++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 48fb482..5dc6bb9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -73,7 +73,6 @@ struct Opts { temp_path: Option, } -#[inline] async fn push_all_profiles( node: &utils::data::Node, node_name: &str, @@ -190,6 +189,8 @@ async fn deploy_all_profiles( /// Returns if the available Nix installation supports flakes #[inline] async fn test_flake_support() -> Result> { + debug!("Checking for flake support"); + Ok(Command::new("nix") .arg("eval") .arg("--expr") @@ -202,6 +203,8 @@ async fn test_flake_support() -> Result> { } async fn check_deployment(supports_flakes: bool, repo: &str, extra_build_args: &[String]) -> () { + info!("Running checks for flake in {}", repo); + let mut c = match supports_flakes { true => Command::new("nix"), false => Command::new("nix-build"), diff --git a/src/utils/deploy.rs b/src/utils/deploy.rs index 1ef7f11..59217df 100644 --- a/src/utils/deploy.rs +++ b/src/utils/deploy.rs @@ -106,6 +106,8 @@ pub async fn deploy_profile( magic_rollback, ); + debug!("Constructed activation command: {}", self_activate_command); + let hostname = match deploy_data.cmd_overrides.hostname { Some(ref x) => x, None => &deploy_data.node.node_settings.hostname, @@ -146,6 +148,11 @@ pub async fn deploy_profile( confirm_command = format!("{} {}", sudo_cmd, confirm_command); } + debug!( + "Attempting to run command to confirm deployment: {}", + confirm_command + ); + let ssh_exit_status = ssh_confirm_command.arg(confirm_command).status().await?; if !ssh_exit_status.success() { -- cgit v1.2.3