forked from DGNum/colmena
Add initial set of tests
This commit is contained in:
parent
114c344dbb
commit
dbd66d7c7c
8 changed files with 288 additions and 1 deletions
17
.github/workflows/build.yml
vendored
Normal file
17
.github/workflows/build.yml
vendored
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
name: Build
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.3.4
|
||||||
|
- uses: cachix/install-nix-action@v12
|
||||||
|
- run: nix-build
|
||||||
|
tests:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2.3.4
|
||||||
|
- uses: cachix/install-nix-action@v12
|
||||||
|
- run: nix-shell dev-shell.nix --run "cargo test"
|
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -18,6 +18,27 @@ dependencies = [
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-stream"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3670df70cbc01729f901f94c887814b3c68db038aad1329a418bae178bc5295c"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream-impl",
|
||||||
|
"futures-core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "async-stream-impl"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3548b8efc9f8e8a5a0a2808c5bd8451a9031b9e5b879a79590304ae928b0a70"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "async-trait"
|
name = "async-trait"
|
||||||
version = "0.1.42"
|
version = "0.1.42"
|
||||||
|
@ -114,6 +135,7 @@ dependencies = [
|
||||||
"sys-info",
|
"sys-info",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
"tokio",
|
"tokio",
|
||||||
|
"tokio-test",
|
||||||
"validator",
|
"validator",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -829,6 +851,30 @@ dependencies = [
|
||||||
"syn",
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-stream"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1981ad97df782ab506a1f43bf82c967326960d278acf3bf8279809648c3ff3ea"
|
||||||
|
dependencies = [
|
||||||
|
"futures-core",
|
||||||
|
"pin-project-lite",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-test"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7c7d205f6f59b03f9e824ac86eaba635a98395f287756ecc8a06464779c399bf"
|
||||||
|
dependencies = [
|
||||||
|
"async-stream",
|
||||||
|
"bytes",
|
||||||
|
"futures-core",
|
||||||
|
"tokio",
|
||||||
|
"tokio-stream",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-bidi"
|
name = "unicode-bidi"
|
||||||
version = "0.3.4"
|
version = "0.3.4"
|
||||||
|
|
|
@ -25,6 +25,7 @@ serde_json = "1.0"
|
||||||
sys-info = "0.7.0"
|
sys-info = "0.7.0"
|
||||||
snafu = "0.6.10"
|
snafu = "0.6.10"
|
||||||
tempfile = "3.1.0"
|
tempfile = "3.1.0"
|
||||||
|
tokio-test = "0.4.0"
|
||||||
validator = { version = "0.12", features = ["derive"] }
|
validator = { version = "0.12", features = ["derive"] }
|
||||||
|
|
||||||
[dependencies.tokio]
|
[dependencies.tokio]
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
# Colmena
|
# Colmena
|
||||||
|
|
||||||
|
![Build](https://github.com/zhaofengli/colmena/workflows/Build/badge.svg)
|
||||||
|
|
||||||
Colmena is a simple, stateless NixOS deployment tool modeled after [NixOps](https://github.com/NixOS/nixops) and [Morph](https://github.com/DBCDK/morph), written in Rust.
|
Colmena is a simple, stateless NixOS deployment tool modeled after [NixOps](https://github.com/NixOS/nixops) and [Morph](https://github.com/DBCDK/morph), written in Rust.
|
||||||
It's a thin wrapper over Nix commands like `nix-instantiate` and `nix-copy-closure`, and supports parallel deployment.
|
It's a thin wrapper over Nix commands like `nix-instantiate` and `nix-copy-closure`, and supports parallel deployment.
|
||||||
|
|
||||||
|
|
|
@ -14,5 +14,8 @@ in rustPlatform.buildRustPackage {
|
||||||
src = ./.;
|
src = ./.;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
cargoSha256 = "1ibhn8bbcx0y9gjl42d9ba478j6a5dr928v0ds61vwn7lbm68dzr";
|
cargoSha256 = "0rkpv9afkg33i1d0yjlq34zrdqy3i6ldbdag0hgsvxi3v3jfg4qv";
|
||||||
|
|
||||||
|
# Recursive Nix is not stable yet
|
||||||
|
doCheck = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,4 +4,7 @@ in pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
rustc cargo
|
rustc cargo
|
||||||
];
|
];
|
||||||
|
shellHook = ''
|
||||||
|
export NIX_PATH=nixpkgs=${pkgs.path}
|
||||||
|
'';
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,9 @@ pub use profile::{Profile, ProfileMap};
|
||||||
pub mod deployment;
|
pub mod deployment;
|
||||||
pub use deployment::{Goal, Target, Deployment};
|
pub use deployment::{Goal, Target, Deployment};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
pub const SYSTEM_PROFILE: &'static str = "/nix/var/nix/profiles/system";
|
pub const SYSTEM_PROFILE: &'static str = "/nix/var/nix/profiles/system";
|
||||||
|
|
||||||
pub type NixResult<T> = Result<T, NixError>;
|
pub type NixResult<T> = Result<T, NixError>;
|
||||||
|
|
212
src/nix/tests.rs
Normal file
212
src/nix/tests.rs
Normal file
|
@ -0,0 +1,212 @@
|
||||||
|
//! Integration-ish tests
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::hash::Hash;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::iter::{FromIterator, Iterator};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
use tempfile::NamedTempFile;
|
||||||
|
use tokio_test::block_on;
|
||||||
|
|
||||||
|
fn set_eq<T>(a: &[T], b: &[T]) -> bool
|
||||||
|
where
|
||||||
|
T: Eq + Hash,
|
||||||
|
{
|
||||||
|
let a: HashSet<_> = HashSet::from_iter(a);
|
||||||
|
let b: HashSet<_> = HashSet::from_iter(b);
|
||||||
|
|
||||||
|
a == b
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An ad-hoc Hive configuration.
|
||||||
|
struct TempHive {
|
||||||
|
hive: Hive,
|
||||||
|
_temp_file: NamedTempFile,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TempHive {
|
||||||
|
pub fn new(text: &str) -> Self {
|
||||||
|
let mut temp_file = NamedTempFile::new().unwrap();
|
||||||
|
temp_file.write_all(text.as_bytes()).unwrap();
|
||||||
|
|
||||||
|
let hive = Hive::new(temp_file.path()).unwrap();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
hive,
|
||||||
|
_temp_file: temp_file,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the configuration is valid.
|
||||||
|
///
|
||||||
|
/// Note that this _does not_ attempt to evaluate `config.toplevel`.
|
||||||
|
pub fn valid(text: &str) {
|
||||||
|
let mut hive = Self::new(text);
|
||||||
|
hive.hive.show_trace(true);
|
||||||
|
assert!(block_on(hive.deployment_info()).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Asserts that the configuration is invalid.
|
||||||
|
///
|
||||||
|
/// Note that this _does not_ attempt to evaluate `config.toplevel`.
|
||||||
|
pub fn invalid(text: &str) {
|
||||||
|
let hive = Self::new(text);
|
||||||
|
assert!(block_on(hive.deployment_info()).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Deref for TempHive {
|
||||||
|
type Target = Hive;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Hive {
|
||||||
|
&self.hive
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// eval.nix tests
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_simple() {
|
||||||
|
let hive = TempHive::new(r#"
|
||||||
|
{
|
||||||
|
defaults = { pkgs, ... }: {
|
||||||
|
environment.systemPackages = with pkgs; [
|
||||||
|
vim wget curl
|
||||||
|
];
|
||||||
|
boot.loader.grub.device = "/dev/sda";
|
||||||
|
fileSystems."/" = {
|
||||||
|
device = "/dev/sda1";
|
||||||
|
fsType = "ext4";
|
||||||
|
};
|
||||||
|
|
||||||
|
deployment.tags = [ "common-tag" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
host-a = { name, nodes, ... }: {
|
||||||
|
networking.hostName = name;
|
||||||
|
time.timeZone = nodes.host-b.config.time.timeZone;
|
||||||
|
|
||||||
|
deployment.tags = [ "a-tag" ];
|
||||||
|
};
|
||||||
|
|
||||||
|
host-b = {
|
||||||
|
deployment = {
|
||||||
|
targetHost = "somehost.tld";
|
||||||
|
targetPort = 1234;
|
||||||
|
targetUser = "luser";
|
||||||
|
};
|
||||||
|
time.timeZone = "America/Los_Angeles";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
let nodes = block_on(hive.deployment_info()).unwrap();
|
||||||
|
|
||||||
|
assert!(set_eq(
|
||||||
|
&["host-a", "host-b"],
|
||||||
|
&nodes.keys().map(String::as_str).collect::<Vec<&str>>(),
|
||||||
|
));
|
||||||
|
|
||||||
|
// host-a
|
||||||
|
assert!(set_eq(
|
||||||
|
&["common-tag", "a-tag"],
|
||||||
|
&nodes["host-a"].tags.iter().map(String::as_str).collect::<Vec<&str>>(),
|
||||||
|
));
|
||||||
|
assert_eq!(Some("host-a"), nodes["host-a"].target_host.as_deref());
|
||||||
|
assert_eq!(None, nodes["host-a"].target_port);
|
||||||
|
assert_eq!("root", &nodes["host-a"].target_user);
|
||||||
|
|
||||||
|
// host-b
|
||||||
|
assert!(set_eq(
|
||||||
|
&["common-tag"],
|
||||||
|
&nodes["host-b"].tags.iter().map(String::as_str).collect::<Vec<&str>>(),
|
||||||
|
));
|
||||||
|
assert_eq!(Some("somehost.tld"), nodes["host-b"].target_host.as_deref());
|
||||||
|
assert_eq!(Some(1234), nodes["host-b"].target_port);
|
||||||
|
assert_eq!("luser", &nodes["host-b"].target_user);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_node_references() {
|
||||||
|
TempHive::valid(r#"
|
||||||
|
with builtins;
|
||||||
|
{
|
||||||
|
host-a = { name, nodes, ... }:
|
||||||
|
assert name == "host-a";
|
||||||
|
assert length (attrNames nodes) == 2;
|
||||||
|
{
|
||||||
|
time.timeZone = "America/Los_Angeles";
|
||||||
|
};
|
||||||
|
host-b = { name, nodes, ... }:
|
||||||
|
assert name == "host-b";
|
||||||
|
assert length (attrNames nodes) == 2;
|
||||||
|
assert nodes.host-a.config.time.timeZone == "America/Los_Angeles";
|
||||||
|
{};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_unknown_option() {
|
||||||
|
TempHive::invalid(r#"
|
||||||
|
{
|
||||||
|
bad = {
|
||||||
|
deployment.noSuchOption = "not kidding";
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_key_text() {
|
||||||
|
TempHive::valid(r#"
|
||||||
|
{
|
||||||
|
test = {
|
||||||
|
deployment.keys.topSecret = {
|
||||||
|
text = "be sure to drink your ovaltine";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_key_command_good() {
|
||||||
|
TempHive::valid(r#"
|
||||||
|
{
|
||||||
|
test = {
|
||||||
|
deployment.keys.elohim = {
|
||||||
|
keyCommand = [ "eternalize" ];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_key_command_bad() {
|
||||||
|
TempHive::invalid(r#"
|
||||||
|
{
|
||||||
|
test = {
|
||||||
|
deployment.keys.elohim = {
|
||||||
|
keyCommand = "transcend";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_key_file() {
|
||||||
|
TempHive::valid(r#"
|
||||||
|
{
|
||||||
|
test = {
|
||||||
|
deployment.keys.l337hax0rwow = {
|
||||||
|
keyFile = "/etc/passwd";
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue