diff --git a/Cargo.lock b/Cargo.lock index cf3d8fbf..7f03f305 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -350,6 +350,7 @@ dependencies = [ "camino", "indoc", "janetrs", + "walkdir", ] [[package]] @@ -795,6 +796,7 @@ dependencies = [ "serde_json", "tracing", "util", + "walkdir", ] [[package]] @@ -1088,6 +1090,7 @@ dependencies = [ "common", "predicates", "pretty_assertions", + "serde_json", "tester", "tracing", "tracing-subscriber", diff --git a/HISTORY.md b/HISTORY.md index 96ada267..d517578b 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -11,13 +11,13 @@ Items marked [*] are breaking changes. ### Commands +- Remove `show` command. [*] +- Remove `-L` option from `apply` and `compile` commands. [*] - Add `doers` command, which dumps a list of doers to stdout. - Add `repl` command, which opens a Janet REPL with the Gurp library loaded into the root environment. - Add `--destroy-everything-you-touch` to `apply` command. - Add `--as-json` option to client mode, for old compile-on-server behaviour. -- Removed `-L` option from `apply` and `compile` commands. [*] -- Removed `show` command. [*] - `describe` command gives more information, and its layout adjusts for the terminal width. - `describe` and `doers` will not use ANSI colouring if `gurp` is part of a @@ -29,13 +29,14 @@ Items marked [*] are breaking changes. ### Doers +- Replace `symlink` doer with `link`, which also handles hard links. [*] +- Helpers like `zone-fs` or `smf-method` are now referred to as `zone/fs` + and `smf/method`. [*] - Add `network-flow` doer. - Add `vlan` doer. - Add `ipnat` doer. - Add `ipfilter` doer. - Add `limitpriv`, `hostid`, `ip-type`, `pool` to `zone` doer. -- Helpers like `zone-fs` or `smf-method` are now referred to as `zone/fs` - and `smf/method`. [*] - Changed syntax of `ip-address/ensure`, `ip-properties/ensure`, and `ip-interface/ensure` to make them all consistent. - Doer documentation is machine-generated from definition files. diff --git a/build_helper/Cargo.toml b/build_helper/Cargo.toml index 97d48a89..12a8dae8 100644 --- a/build_helper/Cargo.toml +++ b/build_helper/Cargo.toml @@ -9,3 +9,4 @@ blake3 = "1.8.3" camino = "1.2.2" indoc = "2.0.7" janetrs = "0.8.0" +walkdir = "2.5.0" diff --git a/build_helper/src/lib.rs b/build_helper/src/lib.rs index d23621b7..1cd837f8 100644 --- a/build_helper/src/lib.rs +++ b/build_helper/src/lib.rs @@ -16,10 +16,13 @@ pub struct ImageHelper { impl ImageHelper { pub fn new(src_files: Vec<&str>, img_name: &str) -> Self { let repo_root = ImageHelper::repo_root(); + let src_dir = repo_root.join("janet").join("src"); - // Emit rerun directives for all source files - for src_file in &src_files { - println!("cargo:rerun-if-changed=../janet/src/{}", src_file); + for entry in walkdir::WalkDir::new(&src_dir) { + let entry = entry.unwrap(); + if entry.path().extension().is_some_and(|e| e == "janet") { + println!("cargo:rerun-if-changed={}", entry.path().display()); + } } Self { @@ -126,9 +129,15 @@ impl ImageHelper { fn source_hash(&self) -> Hash { let mut hasher = blake3::Hasher::new(); - for src_file in &self.src_files { - let mut fh = - fs::File::open(src_file).expect(&format!("cannot read source file {}", src_file)); + let src_dir = self.repo_root.join("janet").join("src"); + let mut entries: Vec<_> = walkdir::WalkDir::new(&src_dir) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().extension().is_some_and(|x| x == "janet")) + .collect(); + entries.sort_by_key(|e| e.path().to_owned()); // stable order + for entry in entries { + let mut fh = fs::File::open(entry.path()).expect("cannot read source file"); std::io::copy(&mut fh, &mut hasher).expect("cannot hash source file"); } hasher.finalize() diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 03507fa2..6f3373a8 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -17,4 +17,5 @@ camino-tempfile = "1.4.1" camino-tempfile-ext = "0.3.3" predicates = "3.1.3" pretty_assertions = "1.4.1" +serde_json = "1.0.149" tester = { path = "../tester" } diff --git a/cli/tests/compile.rs b/cli/tests/compile.rs index e6ccd925..88d35171 100644 --- a/cli/tests/compile.rs +++ b/cli/tests/compile.rs @@ -25,13 +25,18 @@ mod test { let canonical_json = load_fixture(&format!("compile/outputs/{host}.json")); let expected_json = canonical_json.replace(canonical_test_dir, &test_dir); - cargo_bin_cmd!("gurp") + let output = cargo_bin_cmd!("gurp") .arg("compile") .arg(fixture(&format!("compile/inputs/{host}.janet"))) .arg("--format=json") .assert() - .success() - .stdout(expected_json); + .success(); + + let actual: serde_json::Value = + serde_json::from_slice(&output.get_output().stdout).unwrap(); + let expected: serde_json::Value = serde_json::from_str(&expected_json).unwrap(); + + assert_eq!(actual, expected); } } diff --git a/cli/tests/resources/compile/inputs/roles/file-store.janet b/cli/tests/resources/compile/inputs/roles/file-store.janet index 00c9c8c3..ee93d1b1 100644 --- a/cli/tests/resources/compile/inputs/roles/file-store.janet +++ b/cli/tests/resources/compile/inputs/roles/file-store.janet @@ -37,7 +37,7 @@ :compression "lz4"})) (role file-store - (symlink/ensure "/home" :source home-root) + (link/ensure "/home" :source home-root) (section users (zfs/ensure (zfscat globals/fast-pool "export/home/rob") diff --git a/cli/tests/resources/compile/inputs/roles/grafana.janet b/cli/tests/resources/compile/inputs/roles/grafana.janet index 9c464eb5..dfbd2ed8 100644 --- a/cli/tests/resources/compile/inputs/roles/grafana.janet +++ b/cli/tests/resources/compile/inputs/roles/grafana.janet @@ -53,8 +53,8 @@ :content zfs-mount-script :mode "0755") - (symlink/ensure "/etc/runlevels/boot/zfs-mount" - :source zfs-mounter)) + (link/ensure "/etc/runlevels/boot/zfs-mount" + :source zfs-mounter)) (section configure-grafana (def grafana-config @@ -80,8 +80,8 @@ :match "contains" :pattern "need net") - (symlink/ensure "/etc/runlevels/default/grafana" - :source grafana-init) + (link/ensure "/etc/runlevels/default/grafana" + :source grafana-init) (file-line/ensure "/etc/conf.d/grafana" :replace "127.0.0.1" :with "0.0.0.0") diff --git a/cli/tests/resources/compile/inputs/roles/remover.janet b/cli/tests/resources/compile/inputs/roles/remover.janet index 09a9d195..44fc6571 100644 --- a/cli/tests/resources/compile/inputs/roles/remover.janet +++ b/cli/tests/resources/compile/inputs/roles/remover.janet @@ -33,8 +33,8 @@ # svcprop/remove - (symlink/remove "/var/ld/64") - (symlink/remove "/never/existed") + (link/remove "/var/ld/64") + (link/remove "/never/existed") # (user/remove "sys") # Protected user (user/remove "zfssnap") diff --git a/cli/tests/resources/compile/outputs/dev-server.json b/cli/tests/resources/compile/outputs/dev-server.json index b81dad89..894a2aa6 100644 --- a/cli/tests/resources/compile/outputs/dev-server.json +++ b/cli/tests/resources/compile/outputs/dev-server.json @@ -1 +1 @@ -{"metadata":{"name":"dev-server"},"resources":{"ensure":{"cron":[{"_id":"/physical/cron/turn-off-at-night","command":"/usr/sbin/poweroff","day-of-month":"*","day-of-week":"*","hour":1,"minute":0,"month-of-year":"*","name":"turn-off-at-night","role":"physical","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-daily-snapshots","command":"/opt/site/bin/zfs-snap --type=day --omit=\"rpool/zones/*,*/logs,*/var*\" > /var/log/cron_jobs/zfs-daily-snapshots.log 2>&1","day-of-month":"*","day-of-week":"*","hour":12,"minute":0,"month-of-year":"*","name":"zfs-daily-snapshots","role":"zfs-snapshot","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-monthly-snapshots","command":"/opt/site/bin/zfs-snap --type=month --omit=\"rpool/zones/*,*/logs,*/var*\" > /var/log/cron_jobs/zfs-monthly-snapshots.log 2>&1","day-of-month":"*","day-of-week":1,"hour":12,"minute":0,"month-of-year":"*","name":"zfs-monthly-snapshots","role":"zfs-snapshot","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-work-snapshots","command":"/opt/site/bin/zfs-snap --type=time fast/export/home/rob/work > /var/log/cron_jobs/zfs-work-snapshots.log 2>&1","day-of-month":"*","day-of-week":"*","hour":"*","minute":"*/10","month-of-year":"*","name":"zfs-work-snapshots","role":"zfs-snapshot","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-important-snapshots","command":"/opt/site/bin/zfs-snap --type=time --recurse fast/export/home big/export/flac \"*/data*\" \"*/build*\" > /var/log/cron_jobs/zfs-important-snapshots.log 2>&1","day-of-month":"*","day-of-week":"*","hour":"*","minute":"0,30","month-of-year":"*","name":"zfs-important-snapshots","role":"zfs-snapshot","user":"root"}],"directory":[{"_id":"/physical/directory/_var_lib_ntp","group":"daemon","mode":"0755","name":"/var/lib/ntp","owner":"root","role":"physical"},{"_id":"/basenode/directory/_export","group":"sys","mode":"0755","name":"/export","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_export_home","group":"root","mode":"0755","name":"/export/home","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site","group":"root","mode":"0755","name":"/opt/site","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_bin","group":"root","mode":"0755","name":"/opt/site/bin","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_etc","group":"root","mode":"0755","name":"/opt/site/etc","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_method","group":"root","mode":"0755","name":"/opt/site/lib/smf/method","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_manifest","group":"root","mode":"0755","name":"/opt/site/lib/smf/manifest","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_var_log_cron_jobs","group":"daemon","mode":"0775","name":"/var/log/cron_jobs","owner":"root","role":"basenode"}],"file":[{"_id":"/physical/file/_etc_resolv.conf","content":"domain lan.id264.net\nnameserver 192.168.1.53\nnameserver 192.168.1.1\n","group":"root","mode":"0644","name":"/etc/resolv.conf","owner":"root","role":"physical"},{"_id":"/physical/file/ntp-conf","content":"statsdir /var/log/ntpstats\n\nrestrict 127.0.0.1\n\nserver 0.uk.pool.ntp.org iburst\nserver 1.uk.pool.ntp.org iburst\nserver 2.uk.pool.ntp.org iburst\nserver 3.uk.pool.ntp.org iburst\n\ndriftfile /var/lib/ntp/drift\n","group":"root","label":"ntp-conf","mode":"0644","name":"/etc/ntp.conf","owner":"root","role":"physical"},{"_id":"/basenode/file/_etc_sudoers.d_sudo_group","content":"%sysadmin ALL=(ALL:ALL) ALL","group":"root","mode":"0400","name":"/etc/sudoers.d/sudo_group","owner":"root","role":"basenode"},{"_id":"/basenode/file/crondef","content":"CRONLOG=YES\nPATH=/bin:/sbin:/usr/sbin:/opt/oo/bin:/opt/ooce/sbin","group":"sys","label":"crondef","mode":"0644","name":"/etc/default/cron","owner":"root","role":"basenode"},{"_id":"/telegraf/file/executable","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/telegraf/telegraf","group":"root","label":"executable","mode":"0755","name":"/opt/site/bin/telegraf","owner":"root","role":"telegraf"},{"_id":"/telegraf/file/conf","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/telegraf/telegraf.conf.physical","group":"root","label":"conf","mode":"0644","name":"/opt/site/etc/telegraf.conf","owner":"root","role":"telegraf"},{"_id":"/telegraf/file/_opt_site_lib_smf_method_telegraf.sh","content":"#!/bin/ksh\n\n. /lib/svc/share/smf_include.sh\n\n/opt/site/bin/telegraf --config /opt/site/etc/telegraf.conf &\n\nexit $SMF_EXIT_OK\n","group":"root","mode":"0755","name":"/opt/site/lib/smf/method/telegraf.sh","owner":"root","role":"telegraf"},{"_id":"/cron-monitor/file/cron_monitor","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/cron-monitor/cron_monitor_dtrace","group":"root","label":"cron_monitor","mode":"0755","name":"/opt/site/bin/cron_monitor_dtrace","owner":"root","role":"cron-monitor"},{"_id":"/cron-monitor/file/_opt_site_lib_smf_method_cron_monitor_dtrace","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/cron-monitor/cron_monitor_dtrace_method","group":"root","mode":"0755","name":"/opt/site/lib/smf/method/cron_monitor_dtrace","owner":"root","role":"cron-monitor"},{"_id":"/zfs-snapshot/file/_opt_site_bin_zfs-snap","from":"/home/rob/.cargo/bin/zfs-snap","group":"root","mode":"0755","name":"/opt/site/bin/zfs-snap","owner":"root","role":"zfs-snapshot"}],"file-line":[{"_id":"/basenode/file-line/profile-set-vi","label":"profile-set-vi","line":"set -o vi","name":"/etc/profile","role":"basenode"},{"_id":"/basenode/file-line/profile-path","label":"profile-path","line":"PATH=${PATH}:/opt/ooce/bin","name":"/etc/profile","role":"basenode"}],"misc":[{"_id":"/physical/misc/scheduler-FSS","name":"scheduler-FSS","role":"physical","scheduler":"FSS"},{"_id":"/basenode/misc/nfs-domain-lan.id264.net","name":"nfs-domain-lan.id264.net","nfs-domain":"lan.id264.net","role":"basenode"},{"_id":"/file-store/misc/enable-smb-rob","enable-smb":"rob","name":"enable-smb-rob","role":"file-store"},{"_id":"/file-store/misc/enable-smb-klf","enable-smb":"klf","name":"enable-smb-klf","role":"file-store"}],"pkg":[{"_id":"/physical/pkg/service_network_ntpsec","name":"service/network/ntpsec","role":"physical"},{"_id":"/physical/pkg/network_rsync","name":"network/rsync","role":"physical"},{"_id":"/physical/pkg/shell_zsh","name":"shell/zsh","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_bhyve","name":"system/zones/brand/bhyve","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_illumos","name":"system/zones/brand/illumos","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_ipkg","name":"system/zones/brand/ipkg","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_lipkg","name":"system/zones/brand/lipkg","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_lx_platform","name":"system/zones/brand/lx/platform","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_pkgsrc","name":"system/zones/brand/pkgsrc","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_sparse","name":"system/zones/brand/sparse","role":"physical"},{"_id":"/basenode/pkg/ooce_terminal_starship","name":"ooce/terminal/starship","role":"basenode"},{"_id":"/basenode/pkg/shell_zsh","name":"shell/zsh","role":"basenode"},{"_id":"/cron-monitor/pkg/network_netcat","name":"network/netcat","role":"cron-monitor"}],"smf":[{"_id":"/telegraf/smf/telegraf","default-enabled":true,"description":"Run Telegraf agent","fmri":"sysdef/telegraf","name":"telegraf","role":"telegraf","single-instance":true,"start-method":{"context":{"group":"daemon","privileges":"basic,file_dac_search,sys_admin,proc_owner,proc_zone","user":"telegraf"},"exec":"/opt/site/lib/smf/method/telegraf.sh","timeout":60},"stop-method":{"exec":":kill","timeout":10}},{"_id":"/cron-monitor/smf/cron_monitor","default-enabled":true,"description":"DTrace cron monitor","fmri":"sysdef/cron_monitor","name":"cron_monitor","properties":{"contract":{"type":"astring","value":"fixed"},"delay":{"type":"integer","value":10},"max_restarts":{"type":"integer","value":10}},"property-groups":{"restarter":"framework"},"role":"cron-monitor","single-instance":true,"start-method":{"context":{"group":"daemon","privileges":"basic,!file_link_any,dtrace_kernel,dtrace_proc,dtrace_user","user":"cronmon"},"exec":"/opt/site/lib/smf/method/cron_monitor_dtrace","timeout":10},"stop-method":{"exec":":kill","timeout":10}}],"svc":[{"_id":"/physical/svc/svc:_network_ntp:default","name":"svc:/network/ntp:default","reloaded-by":[],"restarted-by":["/physical/file/ntp-conf"],"role":"physical","state":"online"},{"_id":"/basenode/svc/cron","name":"cron","reloaded-by":[],"restarted-by":["/basenode/file/crondef"],"role":"basenode","state":"online"},{"_id":"/telegraf/svc/sysdef_telegraf","name":"sysdef/telegraf","reloaded-by":[],"restarted-by":["/telegraf/file/conf","/telegraf/file/executable"],"role":"telegraf","state":"online"},{"_id":"/cron-monitor/svc/sysdef_cron_monitor","name":"sysdef/cron_monitor","reloaded-by":[],"restarted-by":["47","99","114","111","110","45","109","111","110","105","116","111","114","47","102","105","108","101","47","99","114","111","110","95","109","111","110","105","116","111","114"],"role":"cron-monitor","state":"online"}],"symlink":[{"_id":"/file-store/symlink/_home","name":"/home","role":"file-store","source":"/export/home"}],"user":[{"_id":"/basenode/user/rob","gecos":"Rob Fisher","home-dir":"/home/rob","name":"rob","other-groups":["staff"],"password-hash":"MYPASSWORDHASH","primary-group":"sysadmin","role":"basenode","shell":"/bin/zsh","uid":264},{"_id":"/telegraf/user/telegraf","gecos":"Telegraf pseudo-user","home-dir":"/var/tmp","name":"telegraf","primary-group":"daemon","role":"telegraf","shell":"/bin/false","uid":108},{"_id":"/file-store/user/klf","gecos":"klf","home-dir":"/export/home/klf","name":"klf","password-hash":"some-hash-or-other","primary-group":"staff","role":"file-store","shell":"/bin/zsh","uid":266},{"_id":"/cron-monitor/user/cronmon","gecos":"cron_monitor pseudo-user","home-dir":"/var/tmp","name":"cronmon","primary-group":"daemon","role":"cron-monitor","shell":"/bin/false","uid":107}],"zfs":[{"_id":"/file-store/zfs/fast_export_home_rob","name":"fast/export/home/rob","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"on","mountpoint":"/export/home/rob","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"off"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export_home_rob_work","name":"fast/export/home/rob/work","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"on","mountpoint":"/export/home/rob/work","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"off"},"role":"file-store"},{"_id":"/file-store/zfs/big_user_data_rob","name":"big/user_data/rob","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/user_data/rob","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export_home_klf","name":"fast/export/home/klf","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/home/klf","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=klf"},"role":"file-store"},{"_id":"/file-store/zfs/big_user_data_klf","name":"big/user_data/klf","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/user_data/klf","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/rpool_zones","name":"rpool/zones","properties":{"mountpoint":"/zones"},"role":"file-store"},{"_id":"/file-store/zfs/big","name":"big","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/big_zone","name":"big/zone","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/big_bhyve","name":"big/bhyve","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/big_video","name":"big/video","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/video","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=video"},"role":"file-store"},{"_id":"/file-store/zfs/big_flac","name":"big/flac","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/flac","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=flac"},"role":"file-store"},{"_id":"/file-store/zfs/big_software","name":"big/software","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/software","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_fonts","name":"big/fonts","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/fonts","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_games","name":"big/games","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/games","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_sysdef","name":"big/sysdef","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/sysdef","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_kronos","name":"big/kronos","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/kronos","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_export_pkg_repo","name":"big/export/pkg_repo","properties":{"compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/pkg_repo","setuid":"off"},"role":"file-store"},{"_id":"/file-store/zfs/fast","name":"fast","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_zone","name":"fast/zone","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_user_data","name":"fast/user_data","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export","name":"fast/export","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export_home","name":"fast/export/home","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_mp3","name":"fast/mp3","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/mp3","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=mp3"},"role":"file-store"},{"_id":"/file-store/zfs/fast_photos","name":"fast/photos","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/photos","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=photos"},"role":"file-store"}]},"remove":{}}} +{"metadata":{"name":"dev-server"},"resources":{"ensure":{"cron":[{"_id":"/physical/cron/turn-off-at-night","command":"/usr/sbin/poweroff","day-of-month":"*","day-of-week":"*","hour":1,"minute":0,"month-of-year":"*","name":"turn-off-at-night","role":"physical","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-daily-snapshots","command":"/opt/site/bin/zfs-snap --type=day --omit=\"rpool/zones/*,*/logs,*/var*\" > /var/log/cron_jobs/zfs-daily-snapshots.log 2>&1","day-of-month":"*","day-of-week":"*","hour":12,"minute":0,"month-of-year":"*","name":"zfs-daily-snapshots","role":"zfs-snapshot","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-monthly-snapshots","command":"/opt/site/bin/zfs-snap --type=month --omit=\"rpool/zones/*,*/logs,*/var*\" > /var/log/cron_jobs/zfs-monthly-snapshots.log 2>&1","day-of-month":"*","day-of-week":1,"hour":12,"minute":0,"month-of-year":"*","name":"zfs-monthly-snapshots","role":"zfs-snapshot","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-work-snapshots","command":"/opt/site/bin/zfs-snap --type=time fast/export/home/rob/work > /var/log/cron_jobs/zfs-work-snapshots.log 2>&1","day-of-month":"*","day-of-week":"*","hour":"*","minute":"*/10","month-of-year":"*","name":"zfs-work-snapshots","role":"zfs-snapshot","user":"root"},{"_id":"/zfs-snapshot/cron/zfs-important-snapshots","command":"/opt/site/bin/zfs-snap --type=time --recurse fast/export/home big/export/flac \"*/data*\" \"*/build*\" > /var/log/cron_jobs/zfs-important-snapshots.log 2>&1","day-of-month":"*","day-of-week":"*","hour":"*","minute":"0,30","month-of-year":"*","name":"zfs-important-snapshots","role":"zfs-snapshot","user":"root"}],"directory":[{"_id":"/physical/directory/_var_lib_ntp","group":"daemon","mode":"0755","name":"/var/lib/ntp","owner":"root","role":"physical"},{"_id":"/basenode/directory/_export","group":"sys","mode":"0755","name":"/export","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_export_home","group":"root","mode":"0755","name":"/export/home","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site","group":"root","mode":"0755","name":"/opt/site","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_bin","group":"root","mode":"0755","name":"/opt/site/bin","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_etc","group":"root","mode":"0755","name":"/opt/site/etc","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_method","group":"root","mode":"0755","name":"/opt/site/lib/smf/method","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_manifest","group":"root","mode":"0755","name":"/opt/site/lib/smf/manifest","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_var_log_cron_jobs","group":"daemon","mode":"0775","name":"/var/log/cron_jobs","owner":"root","role":"basenode"}],"file":[{"_id":"/physical/file/_etc_resolv.conf","content":"domain lan.id264.net\nnameserver 192.168.1.53\nnameserver 192.168.1.1\n","group":"root","mode":"0644","name":"/etc/resolv.conf","owner":"root","role":"physical"},{"_id":"/physical/file/ntp-conf","content":"statsdir /var/log/ntpstats\n\nrestrict 127.0.0.1\n\nserver 0.uk.pool.ntp.org iburst\nserver 1.uk.pool.ntp.org iburst\nserver 2.uk.pool.ntp.org iburst\nserver 3.uk.pool.ntp.org iburst\n\ndriftfile /var/lib/ntp/drift\n","group":"root","label":"ntp-conf","mode":"0644","name":"/etc/ntp.conf","owner":"root","role":"physical"},{"_id":"/basenode/file/_etc_sudoers.d_sudo_group","content":"%sysadmin ALL=(ALL:ALL) ALL","group":"root","mode":"0400","name":"/etc/sudoers.d/sudo_group","owner":"root","role":"basenode"},{"_id":"/basenode/file/crondef","content":"CRONLOG=YES\nPATH=/bin:/sbin:/usr/sbin:/opt/oo/bin:/opt/ooce/sbin","group":"sys","label":"crondef","mode":"0644","name":"/etc/default/cron","owner":"root","role":"basenode"},{"_id":"/telegraf/file/executable","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/telegraf/telegraf","group":"root","label":"executable","mode":"0755","name":"/opt/site/bin/telegraf","owner":"root","role":"telegraf"},{"_id":"/telegraf/file/conf","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/telegraf/telegraf.conf.physical","group":"root","label":"conf","mode":"0644","name":"/opt/site/etc/telegraf.conf","owner":"root","role":"telegraf"},{"_id":"/telegraf/file/_opt_site_lib_smf_method_telegraf.sh","content":"#!/bin/ksh\n\n. /lib/svc/share/smf_include.sh\n\n/opt/site/bin/telegraf --config /opt/site/etc/telegraf.conf &\n\nexit $SMF_EXIT_OK\n","group":"root","mode":"0755","name":"/opt/site/lib/smf/method/telegraf.sh","owner":"root","role":"telegraf"},{"_id":"/cron-monitor/file/cron_monitor","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/cron-monitor/cron_monitor_dtrace","group":"root","label":"cron_monitor","mode":"0755","name":"/opt/site/bin/cron_monitor_dtrace","owner":"root","role":"cron-monitor"},{"_id":"/cron-monitor/file/_opt_site_lib_smf_method_cron_monitor_dtrace","from":"/home/rob/work/gurp/cli/tests/resources/compile/inputs/files/cron-monitor/cron_monitor_dtrace_method","group":"root","mode":"0755","name":"/opt/site/lib/smf/method/cron_monitor_dtrace","owner":"root","role":"cron-monitor"},{"_id":"/zfs-snapshot/file/_opt_site_bin_zfs-snap","from":"/home/rob/.cargo/bin/zfs-snap","group":"root","mode":"0755","name":"/opt/site/bin/zfs-snap","owner":"root","role":"zfs-snapshot"}],"file-line":[{"_id":"/basenode/file-line/profile-set-vi","label":"profile-set-vi","line":"set -o vi","name":"/etc/profile","role":"basenode"},{"_id":"/basenode/file-line/profile-path","label":"profile-path","line":"PATH=${PATH}:/opt/ooce/bin","name":"/etc/profile","role":"basenode"}],"misc":[{"_id":"/physical/misc/scheduler-FSS","name":"scheduler-FSS","role":"physical","scheduler":"FSS"},{"_id":"/basenode/misc/nfs-domain-lan.id264.net","name":"nfs-domain-lan.id264.net","nfs-domain":"lan.id264.net","role":"basenode"},{"_id":"/file-store/misc/enable-smb-rob","enable-smb":"rob","name":"enable-smb-rob","role":"file-store"},{"_id":"/file-store/misc/enable-smb-klf","enable-smb":"klf","name":"enable-smb-klf","role":"file-store"}],"pkg":[{"_id":"/physical/pkg/service_network_ntpsec","name":"service/network/ntpsec","role":"physical"},{"_id":"/physical/pkg/network_rsync","name":"network/rsync","role":"physical"},{"_id":"/physical/pkg/shell_zsh","name":"shell/zsh","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_bhyve","name":"system/zones/brand/bhyve","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_illumos","name":"system/zones/brand/illumos","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_ipkg","name":"system/zones/brand/ipkg","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_lipkg","name":"system/zones/brand/lipkg","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_lx_platform","name":"system/zones/brand/lx/platform","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_pkgsrc","name":"system/zones/brand/pkgsrc","role":"physical"},{"_id":"/physical/pkg/system_zones_brand_sparse","name":"system/zones/brand/sparse","role":"physical"},{"_id":"/basenode/pkg/ooce_terminal_starship","name":"ooce/terminal/starship","role":"basenode"},{"_id":"/basenode/pkg/shell_zsh","name":"shell/zsh","role":"basenode"},{"_id":"/cron-monitor/pkg/network_netcat","name":"network/netcat","role":"cron-monitor"}],"smf":[{"_id":"/telegraf/smf/telegraf","default-enabled":true,"description":"Run Telegraf agent","fmri":"sysdef/telegraf","name":"telegraf","role":"telegraf","single-instance":true,"start-method":{"context":{"group":"daemon","privileges":"basic,file_dac_search,sys_admin,proc_owner,proc_zone","user":"telegraf"},"exec":"/opt/site/lib/smf/method/telegraf.sh","timeout":60},"stop-method":{"exec":":kill","timeout":10}},{"_id":"/cron-monitor/smf/cron_monitor","default-enabled":true,"description":"DTrace cron monitor","fmri":"sysdef/cron_monitor","name":"cron_monitor","properties":{"contract":{"type":"astring","value":"fixed"},"delay":{"type":"integer","value":10},"max_restarts":{"type":"integer","value":10}},"property-groups":{"restarter":"framework"},"role":"cron-monitor","single-instance":true,"start-method":{"context":{"group":"daemon","privileges":"basic,!file_link_any,dtrace_kernel,dtrace_proc,dtrace_user","user":"cronmon"},"exec":"/opt/site/lib/smf/method/cron_monitor_dtrace","timeout":10},"stop-method":{"exec":":kill","timeout":10}}],"svc":[{"_id":"/physical/svc/svc:_network_ntp:default","name":"svc:/network/ntp:default","reloaded-by":[],"restarted-by":["/physical/file/ntp-conf"],"role":"physical","state":"online"},{"_id":"/basenode/svc/cron","name":"cron","reloaded-by":[],"restarted-by":["/basenode/file/crondef"],"role":"basenode","state":"online"},{"_id":"/telegraf/svc/sysdef_telegraf","name":"sysdef/telegraf","reloaded-by":[],"restarted-by":["/telegraf/file/conf","/telegraf/file/executable"],"role":"telegraf","state":"online"},{"_id":"/cron-monitor/svc/sysdef_cron_monitor","name":"sysdef/cron_monitor","reloaded-by":[],"restarted-by":["47","99","114","111","110","45","109","111","110","105","116","111","114","47","102","105","108","101","47","99","114","111","110","95","109","111","110","105","116","111","114"],"role":"cron-monitor","state":"online"}],"link":[{"_id":"/file-store/link/_home","type":"symbolic","name":"/home","role":"file-store","source":"/export/home"}],"user":[{"_id":"/basenode/user/rob","gecos":"Rob Fisher","home-dir":"/home/rob","name":"rob","other-groups":["staff"],"password-hash":"MYPASSWORDHASH","primary-group":"sysadmin","role":"basenode","shell":"/bin/zsh","uid":264},{"_id":"/telegraf/user/telegraf","gecos":"Telegraf pseudo-user","home-dir":"/var/tmp","name":"telegraf","primary-group":"daemon","role":"telegraf","shell":"/bin/false","uid":108},{"_id":"/file-store/user/klf","gecos":"klf","home-dir":"/export/home/klf","name":"klf","password-hash":"some-hash-or-other","primary-group":"staff","role":"file-store","shell":"/bin/zsh","uid":266},{"_id":"/cron-monitor/user/cronmon","gecos":"cron_monitor pseudo-user","home-dir":"/var/tmp","name":"cronmon","primary-group":"daemon","role":"cron-monitor","shell":"/bin/false","uid":107}],"zfs":[{"_id":"/file-store/zfs/fast_export_home_rob","name":"fast/export/home/rob","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"on","mountpoint":"/export/home/rob","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"off"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export_home_rob_work","name":"fast/export/home/rob/work","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"on","mountpoint":"/export/home/rob/work","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"off"},"role":"file-store"},{"_id":"/file-store/zfs/big_user_data_rob","name":"big/user_data/rob","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/user_data/rob","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export_home_klf","name":"fast/export/home/klf","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/home/klf","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=klf"},"role":"file-store"},{"_id":"/file-store/zfs/big_user_data_klf","name":"big/user_data/klf","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/user_data/klf","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/rpool_zones","name":"rpool/zones","properties":{"mountpoint":"/zones"},"role":"file-store"},{"_id":"/file-store/zfs/big","name":"big","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/big_zone","name":"big/zone","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/big_bhyve","name":"big/bhyve","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/big_video","name":"big/video","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/video","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=video"},"role":"file-store"},{"_id":"/file-store/zfs/big_flac","name":"big/flac","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/flac","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=flac"},"role":"file-store"},{"_id":"/file-store/zfs/big_software","name":"big/software","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/software","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_fonts","name":"big/fonts","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/fonts","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_games","name":"big/games","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/games","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_sysdef","name":"big/sysdef","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/sysdef","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_kronos","name":"big/kronos","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/kronos","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.9,ro=@192.168.1.0/24"},"role":"file-store"},{"_id":"/file-store/zfs/big_export_pkg_repo","name":"big/export/pkg_repo","properties":{"compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/pkg_repo","setuid":"off"},"role":"file-store"},{"_id":"/file-store/zfs/fast","name":"fast","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_zone","name":"fast/zone","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_user_data","name":"fast/user_data","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export","name":"fast/export","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_export_home","name":"fast/export/home","properties":{"mountpoint":"none"},"role":"file-store"},{"_id":"/file-store/zfs/fast_mp3","name":"fast/mp3","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/mp3","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=mp3"},"role":"file-store"},{"_id":"/file-store/zfs/fast_photos","name":"fast/photos","properties":{"atime":"off","compression":"lz4","devices":"off","exec":"off","mountpoint":"/export/photos","setuid":"off","sharenfs":"root=@192.168.1.9/32,rw=@192.168.1.0/24","sharesmb":"name=photos"},"role":"file-store"}]},"remove":{}}} diff --git a/cli/tests/resources/compile/outputs/grafana.json b/cli/tests/resources/compile/outputs/grafana.json index b7ff1d66..09541bd4 100644 --- a/cli/tests/resources/compile/outputs/grafana.json +++ b/cli/tests/resources/compile/outputs/grafana.json @@ -1 +1 @@ -{"metadata":{"name":"grafana"},"resources":{"ensure":{"apk":[{"_id":"/grafana/apk/grafana","name":"grafana","role":"grafana"}],"file":[{"_id":"/grafana/file/_etc_periodic_15min_gurp","content":"#!/bin/sh\n\n/var/tmp/gurp \\\n apply \\\n --metrics-to=metrics \\\n /home/rob/work/my-gurp/zone-grafana.janet \\\n>/var/log/gurp.log 2>&1\n","group":"root","mode":"0755","name":"/etc/periodic/15min/gurp","owner":"root","role":"grafana"},{"_id":"/grafana/file/_etc_init.d_zfs-mount","content":"#!/sbin/openrc-run\n\ndescription=\"Mount delegated ZFS dataset\"\n\ndepend()\n{\n # run as early as possible, only after local filesystems\n need localmount\n before *\n}\n\nstart()\n{\n ebegin \"Mounting ZFS dataset\"\n /native/usr/sbin/zfs mount -a\n eend $?\n}","group":"root","mode":"0755","name":"/etc/init.d/zfs-mount","owner":"root","role":"grafana"},{"_id":"/grafana/file/_etc_grafana.ini","from-struct":{"database":{"host":"mysql","name":"grafana","password":"dummy-password","type":"mysql","user":"grafana"},"log":{"level":"info","mode":"file"},"metrics":{"enabled":true},"news":{"news_feed_enabled":false},"paths":{"data":"/var/lib/grafana","logs":"/var/log/grafana","plugins":"/var/lib/grafana/plugins","provisioning":"conf/provisioning"},"server":{"http_port":3000,"protocol":"http"}},"group":"root","mode":"0644","name":"/etc/grafana.ini","owner":"root","role":"grafana","to-format":"ini"}],"file-line":[{"_id":"/grafana/file-line/_etc_conf.d_grafana","name":"/etc/conf.d/grafana","replace":"127.0.0.1","role":"grafana","with":"0.0.0.0"}],"symlink":[{"_id":"/grafana/symlink/_etc_runlevels_boot_zfs-mount","name":"/etc/runlevels/boot/zfs-mount","role":"grafana","source":"/etc/init.d/zfs-mount"},{"_id":"/grafana/symlink/_etc_runlevels_default_grafana","name":"/etc/runlevels/default/grafana","role":"grafana","source":"/etc/init.d/grafana"}],"zfs":[{"_id":"/grafana/zfs/fast","name":"fast","properties":{"mountpoint":"none"},"role":"grafana"},{"_id":"/grafana/zfs/fast_data","name":"fast/data","properties":{"mountpoint":"/var/lib/grafana"},"role":"grafana"}]},"remove":{"file-line":[{"_id":"/grafana/file-line/_etc_init.d_grafana","apply-to":"all","match":"contains","name":"/etc/init.d/grafana","pattern":"need net","role":"grafana"}]}}} +{"metadata":{"name":"grafana"},"resources":{"ensure":{"apk":[{"_id":"/grafana/apk/grafana","name":"grafana","role":"grafana"}],"file":[{"_id":"/grafana/file/_etc_periodic_15min_gurp","content":"#!/bin/sh\n\n/var/tmp/gurp \\\n apply \\\n --metrics-to=metrics \\\n /home/rob/work/my-gurp/zone-grafana.janet \\\n>/var/log/gurp.log 2>&1\n","group":"root","mode":"0755","name":"/etc/periodic/15min/gurp","owner":"root","role":"grafana"},{"_id":"/grafana/file/_etc_init.d_zfs-mount","content":"#!/sbin/openrc-run\n\ndescription=\"Mount delegated ZFS dataset\"\n\ndepend()\n{\n # run as early as possible, only after local filesystems\n need localmount\n before *\n}\n\nstart()\n{\n ebegin \"Mounting ZFS dataset\"\n /native/usr/sbin/zfs mount -a\n eend $?\n}","group":"root","mode":"0755","name":"/etc/init.d/zfs-mount","owner":"root","role":"grafana"},{"_id":"/grafana/file/_etc_grafana.ini","from-struct":{"database":{"host":"mysql","name":"grafana","password":"dummy-password","type":"mysql","user":"grafana"},"log":{"level":"info","mode":"file"},"metrics":{"enabled":true},"news":{"news_feed_enabled":false},"paths":{"data":"/var/lib/grafana","logs":"/var/log/grafana","plugins":"/var/lib/grafana/plugins","provisioning":"conf/provisioning"},"server":{"http_port":3000,"protocol":"http"}},"group":"root","mode":"0644","name":"/etc/grafana.ini","owner":"root","role":"grafana","to-format":"ini"}],"file-line":[{"_id":"/grafana/file-line/_etc_conf.d_grafana","name":"/etc/conf.d/grafana","replace":"127.0.0.1","role":"grafana","with":"0.0.0.0"}],"link":[{"_id":"/grafana/link/_etc_runlevels_boot_zfs-mount","type":"symbolic","name":"/etc/runlevels/boot/zfs-mount","role":"grafana","source":"/etc/init.d/zfs-mount"},{"_id":"/grafana/link/_etc_runlevels_default_grafana","name":"/etc/runlevels/default/grafana","role":"grafana","source":"/etc/init.d/grafana"}],"zfs":[{"_id":"/grafana/zfs/fast","name":"fast","properties":{"mountpoint":"none"},"role":"grafana"},{"_id":"/grafana/zfs/fast_data","name":"fast/data","properties":{"mountpoint":"/var/lib/grafana"},"role":"grafana"}]},"remove":{"file-line":[{"_id":"/grafana/file-line/_etc_init.d_grafana","apply-to":"all","match":"contains","name":"/etc/init.d/grafana","pattern":"need net","role":"grafana"}]}}} diff --git a/cli/tests/resources/compile/outputs/remover.json b/cli/tests/resources/compile/outputs/remover.json index 754fdd82..215ba275 100644 --- a/cli/tests/resources/compile/outputs/remover.json +++ b/cli/tests/resources/compile/outputs/remover.json @@ -1 +1 @@ -{"metadata":{"name":"remover"},"resources":{"ensure":{"directory":[{"_id":"/basenode/directory/_export","group":"sys","mode":"0755","name":"/export","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_export_home","group":"root","mode":"0755","name":"/export/home","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site","group":"root","mode":"0755","name":"/opt/site","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_bin","group":"root","mode":"0755","name":"/opt/site/bin","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_etc","group":"root","mode":"0755","name":"/opt/site/etc","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_method","group":"root","mode":"0755","name":"/opt/site/lib/smf/method","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_manifest","group":"root","mode":"0755","name":"/opt/site/lib/smf/manifest","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_var_log_cron_jobs","group":"daemon","mode":"0775","name":"/var/log/cron_jobs","owner":"root","role":"basenode"}],"file":[{"_id":"/basenode/file/_etc_sudoers.d_sudo_group","content":"%sysadmin ALL=(ALL:ALL) ALL","group":"root","mode":"0400","name":"/etc/sudoers.d/sudo_group","owner":"root","role":"basenode"},{"_id":"/basenode/file/crondef","content":"CRONLOG=YES\nPATH=/bin:/sbin:/usr/sbin:/opt/oo/bin:/opt/ooce/sbin","group":"sys","label":"crondef","mode":"0644","name":"/etc/default/cron","owner":"root","role":"basenode"}],"file-line":[{"_id":"/basenode/file-line/profile-set-vi","label":"profile-set-vi","line":"set -o vi","name":"/etc/profile","role":"basenode"},{"_id":"/basenode/file-line/profile-path","label":"profile-path","line":"PATH=${PATH}:/opt/ooce/bin","name":"/etc/profile","role":"basenode"}],"misc":[{"_id":"/basenode/misc/nfs-domain-lan.id264.net","name":"nfs-domain-lan.id264.net","nfs-domain":"lan.id264.net","role":"basenode"}],"pkg":[{"_id":"/basenode/pkg/ooce_terminal_starship","name":"ooce/terminal/starship","role":"basenode"},{"_id":"/basenode/pkg/shell_zsh","name":"shell/zsh","role":"basenode"}],"svc":[{"_id":"/basenode/svc/cron","name":"cron","reloaded-by":[],"restarted-by":["/basenode/file/crondef"],"role":"basenode","state":"online"}],"user":[{"_id":"/basenode/user/rob","gecos":"Rob Fisher","home-dir":"/home/rob","name":"rob","other-groups":["staff"],"password-hash":"MYPASSWORDHASH","primary-group":"sysadmin","role":"basenode","shell":"/bin/zsh","uid":264}]},"remove":{"directory":[{"_id":"/remover/directory/_var_krb5","name":"/var/krb5","role":"remover"},{"_id":"/remover/directory/_var_yp_binding","name":"/var/yp/binding","role":"remover"},{"_id":"/remover/directory/_never_existed","name":"/never/existed","role":"remover"}],"file":[{"_id":"/remover/file/_var_yp_aliases","name":"/var/yp/aliases","role":"remover"},{"_id":"/remover/file/_var_yp_nicknames","name":"/var/yp/nicknames","role":"remover"},{"_id":"/remover/file/_never_existed","name":"/never/existed","role":"remover"}],"group":[{"_id":"/remover/group/gdm","name":"gdm","role":"remover"},{"_id":"/remover/group/upnp","name":"upnp","role":"remover"},{"_id":"/remover/group/never-existed","name":"never-existed","role":"remover"}],"pkg":[{"_id":"/remover/pkg/compress_unzip","name":"compress/unzip","role":"remover"},{"_id":"/remover/pkg/never_existed","name":"never/existed","role":"remover"}],"publisher":[{"_id":"/remover/publisher/extra.omnios","name":"extra.omnios","role":"remover"},{"_id":"/remover/publisher/never.existed","name":"never.existed","role":"remover"}],"smf":[{"_id":"/remover/smf/svc:_network_ssh:default","name":"svc:/network/ssh:default","role":"remover"},{"_id":"/remover/smf/svc:_never_existed","name":"svc:/never/existed","role":"remover"}],"symlink":[{"_id":"/remover/symlink/_var_ld_64","name":"/var/ld/64","role":"remover"},{"_id":"/remover/symlink/_never_existed","name":"/never/existed","role":"remover"}],"user":[{"_id":"/remover/user/zfssnap","name":"zfssnap","role":"remover"},{"_id":"/remover/user/upnp","name":"upnp","role":"remover"},{"_id":"/remover/user/never-existed","name":"never-existed","role":"remover"}],"zfs":[{"_id":"/remover/zfs/never_existed","name":"never/existed","role":"remover"}]}}} +{"metadata":{"name":"remover"},"resources":{"ensure":{"directory":[{"_id":"/basenode/directory/_export","group":"sys","mode":"0755","name":"/export","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_export_home","group":"root","mode":"0755","name":"/export/home","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site","group":"root","mode":"0755","name":"/opt/site","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_bin","group":"root","mode":"0755","name":"/opt/site/bin","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_etc","group":"root","mode":"0755","name":"/opt/site/etc","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_method","group":"root","mode":"0755","name":"/opt/site/lib/smf/method","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_opt_site_lib_smf_manifest","group":"root","mode":"0755","name":"/opt/site/lib/smf/manifest","owner":"root","role":"basenode"},{"_id":"/basenode/directory/_var_log_cron_jobs","group":"daemon","mode":"0775","name":"/var/log/cron_jobs","owner":"root","role":"basenode"}],"file":[{"_id":"/basenode/file/_etc_sudoers.d_sudo_group","content":"%sysadmin ALL=(ALL:ALL) ALL","group":"root","mode":"0400","name":"/etc/sudoers.d/sudo_group","owner":"root","role":"basenode"},{"_id":"/basenode/file/crondef","content":"CRONLOG=YES\nPATH=/bin:/sbin:/usr/sbin:/opt/oo/bin:/opt/ooce/sbin","group":"sys","label":"crondef","mode":"0644","name":"/etc/default/cron","owner":"root","role":"basenode"}],"file-line":[{"_id":"/basenode/file-line/profile-set-vi","label":"profile-set-vi","line":"set -o vi","name":"/etc/profile","role":"basenode"},{"_id":"/basenode/file-line/profile-path","label":"profile-path","line":"PATH=${PATH}:/opt/ooce/bin","name":"/etc/profile","role":"basenode"}],"misc":[{"_id":"/basenode/misc/nfs-domain-lan.id264.net","name":"nfs-domain-lan.id264.net","nfs-domain":"lan.id264.net","role":"basenode"}],"pkg":[{"_id":"/basenode/pkg/ooce_terminal_starship","name":"ooce/terminal/starship","role":"basenode"},{"_id":"/basenode/pkg/shell_zsh","name":"shell/zsh","role":"basenode"}],"svc":[{"_id":"/basenode/svc/cron","name":"cron","reloaded-by":[],"restarted-by":["/basenode/file/crondef"],"role":"basenode","state":"online"}],"user":[{"_id":"/basenode/user/rob","gecos":"Rob Fisher","home-dir":"/home/rob","name":"rob","other-groups":["staff"],"password-hash":"MYPASSWORDHASH","primary-group":"sysadmin","role":"basenode","shell":"/bin/zsh","uid":264}]},"remove":{"directory":[{"_id":"/remover/directory/_var_krb5","name":"/var/krb5","role":"remover"},{"_id":"/remover/directory/_var_yp_binding","name":"/var/yp/binding","role":"remover"},{"_id":"/remover/directory/_never_existed","name":"/never/existed","role":"remover"}],"file":[{"_id":"/remover/file/_var_yp_aliases","name":"/var/yp/aliases","role":"remover"},{"_id":"/remover/file/_var_yp_nicknames","name":"/var/yp/nicknames","role":"remover"},{"_id":"/remover/file/_never_existed","name":"/never/existed","role":"remover"}],"group":[{"_id":"/remover/group/gdm","name":"gdm","role":"remover"},{"_id":"/remover/group/upnp","name":"upnp","role":"remover"},{"_id":"/remover/group/never-existed","name":"never-existed","role":"remover"}],"pkg":[{"_id":"/remover/pkg/compress_unzip","name":"compress/unzip","role":"remover"},{"_id":"/remover/pkg/never_existed","name":"never/existed","role":"remover"}],"publisher":[{"_id":"/remover/publisher/extra.omnios","name":"extra.omnios","role":"remover"},{"_id":"/remover/publisher/never.existed","name":"never.existed","role":"remover"}],"smf":[{"_id":"/remover/smf/svc:_network_ssh:default","name":"svc:/network/ssh:default","role":"remover"},{"_id":"/remover/smf/svc:_never_existed","name":"svc:/never/existed","role":"remover"}],"link":[{"_id":"/remover/link/_var_ld_64","name":"/var/ld/64","role":"remover"},{"_id":"/remover/link/_never_existed","name":"/never/existed","role":"remover"}],"user":[{"_id":"/remover/user/zfssnap","name":"zfssnap","role":"remover"},{"_id":"/remover/user/upnp","name":"upnp","role":"remover"},{"_id":"/remover/user/never-existed","name":"never-existed","role":"remover"}],"zfs":[{"_id":"/remover/zfs/never_existed","name":"never/existed","role":"remover"}]}}} diff --git a/doc/doers/link.md b/doc/doers/link.md new file mode 100644 index 00000000..7566ee24 --- /dev/null +++ b/doc/doers/link.md @@ -0,0 +1,52 @@ +# link + +Create and remove links. + +## Resource Name + +Qualified path to the link that will be created (`:string`) + +## link/ensure + +```janet +(link/ensure "/symlink/is/here" + :label "example-symlink" + :source "/link/points/here") +``` + +```janet +(link/ensure "/link/is/here" + :type "hard" + :source "/link/points/here") +``` + +### Mandatory Properties + +| key | type | description | default | +|-------|--------|---------------|-----------| +| `:source` | `string` | The file to which we will link | | +| `:type` | `string` | The type of link: symbolic or hard | `"symbolic"` | + +### Optional Properties + +None + +## link/remove + +```janet +(link/remove "/dont/want/this/link") +``` + +### Mandatory Properties + +None + +### Optional Properties + +None + +## Notes + +- If the source doesn't exist, you get an error. +- Files and directories are ensured before links, so you can link Gurp-managed resources. +- If the link exists and points to the wrong file, it will be removed and re-created, and if it exists but is not a link, that's an error. diff --git a/doc/doers/symlink.md b/doc/doers/symlink.md deleted file mode 100644 index 7087ceaa..00000000 --- a/doc/doers/symlink.md +++ /dev/null @@ -1,45 +0,0 @@ -# symlink - -Create and remove symbolic links. - -## Resource Name - -Qualified path to the link that will be created (`:string`) - -## symlink/ensure - -```janet -(symlink/ensure "/link/is/here" - :label "example-link" - :source "/link/points/here") -``` - -### Mandatory Properties - -| key | type | description | default | -|-------|--------|---------------|-----------| -| `:source` | `string` | The file the symlink points to | | - -### Optional Properties - -None - -## symlink/remove - -```janet -(symlink/remove "/dont/want/this/link") -``` - -### Mandatory Properties - -None - -### Optional Properties - -None - -## Notes - -- If the :source doesn't exist, you get an error. -- Files are ensured before links, so you can make a file and link to it. -- If the link exists and points to the wrong file, it will be removed and re-created, and if it exists but is not a link, that's an error. diff --git a/doers/src/lib.rs b/doers/src/lib.rs index 40b3f260..7e90d48e 100644 --- a/doers/src/lib.rs +++ b/doers/src/lib.rs @@ -15,6 +15,7 @@ pub mod ip_interface; pub mod ip_properties; pub mod ipfilter; pub mod ipnat; +pub mod link; pub mod misc; pub mod network_flow; pub mod pkg; @@ -24,7 +25,6 @@ pub mod route; pub mod smf; pub mod svc; pub mod svcprop; -pub mod symlink; pub mod types; pub mod user; pub mod vlan; diff --git a/doers/src/link.rs b/doers/src/link.rs new file mode 100644 index 00000000..15dc1e39 --- /dev/null +++ b/doers/src/link.rs @@ -0,0 +1,336 @@ +use anyhow::{bail, ensure}; +use camino::{Utf8Path, Utf8PathBuf}; +use common::constants::{ONE_RESOURCE_NO_CHANGE, ONE_RESOURCE_ONE_CHANGE}; +use common::types::{ApplyOpts, ApplySummary}; +use serde::Deserialize; +use std::fmt::Debug; +use std::fs; +use std::os::unix; + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct GurpLinkEnsure { + #[serde(rename = "_id")] + pub id: String, + #[serde(rename = "name")] + pub target: Utf8PathBuf, + pub source: Utf8PathBuf, + #[serde(rename = "type")] + pub link_type: String, +} + +#[derive(Deserialize, Debug, PartialEq, Eq)] +pub struct GurpLinkRemove { + #[serde(rename = "_id")] + pub id: String, + #[serde(rename = "name")] + pub path: Utf8PathBuf, +} + +impl GurpLinkEnsure { + pub fn apply(&self, opts: &ApplyOpts) -> anyhow::Result { + let target = &self.target; + let source = &self.source; + + ensure!(source.exists(), "source not found: {source}"); + + if !target.exists() { + tracing::info!("creating link: {} -> {}", target, source); + return_if_noop!(opts); + + self.create_link(source, target) + } else { + let current_matches = match self.link_type.as_str() { + "symbolic" => { + if target.is_symlink() { + let current_source = target.read_link_utf8()?; + current_source == *source + } else { + false + } + } + "hard" => self.are_hard_linked(source, target)?, + other => bail!("unknown link type: {other}"), + }; + + if current_matches { + tracing::debug!("no change: {}", self.target); + Ok(ONE_RESOURCE_NO_CHANGE) + } else { + // Need to recreate the link + if target.is_symlink() { + let current_source = target.read_link_utf8()?; + tracing::info!( + "change link source: [{}] {} -> {}", + target, + ¤t_source, + source + ); + } else { + tracing::info!( + "change link source: [{}] (existing file) -> {}", + target, + source + ); + } + return_if_noop!(opts); + + fs::remove_file(target)?; + self.create_link(source, target) + } + } + } + + fn create_link(&self, source: &Utf8Path, target: &Utf8Path) -> anyhow::Result { + match self.link_type.as_str() { + "symbolic" => unix::fs::symlink(source, target)?, + "hard" => fs::hard_link(source, target)?, + other => bail!("unknown link type: {other}"), + } + + Ok(ONE_RESOURCE_ONE_CHANGE) + } + + fn are_hard_linked(&self, source: &Utf8Path, target: &Utf8Path) -> anyhow::Result { + use std::os::unix::fs::MetadataExt; + + let source_metadata = fs::metadata(source)?; + let target_metadata = fs::metadata(target)?; + + Ok(source_metadata.ino() == target_metadata.ino() + && source_metadata.dev() == target_metadata.dev()) + } +} + +impl GurpLinkRemove { + pub fn apply(&self, opts: &ApplyOpts) -> anyhow::Result { + if self.path.exists() { + tracing::info!("removing link: {}", self.path); + return_if_noop!(opts); + + fs::remove_file(&self.path)?; + Ok(ONE_RESOURCE_ONE_CHANGE) + } else { + tracing::debug!("not present: {}", self.path); + Ok(ONE_RESOURCE_NO_CHANGE) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use camino_tempfile_ext::prelude::*; + use common::constants::ONE_RESOURCE_NOOP; + use pretty_assertions::assert_eq; + use std::os::unix; + use tester::{defopts, defopts_noop, deserialized_example, janet2json}; + + #[test] + fn test_deserialize_link_ensure_01() { + assert_eq!( + GurpLinkEnsure { + id: "/NO-ROLE/link/example-symlink".to_owned(), + target: Utf8PathBuf::from("/symlink/is/here"), + source: Utf8PathBuf::from("/link/points/here"), + link_type: "symbolic".to_owned(), + }, + deserialized_example("link/ensure-01.janet") + ); + } + + #[test] + fn test_deserialize_link_ensure_02() { + assert_eq!( + GurpLinkEnsure { + id: "/NO-ROLE/link/_link_is_here".to_owned(), + target: Utf8PathBuf::from("/link/is/here"), + source: Utf8PathBuf::from("/link/points/here"), + link_type: "hard".to_owned(), + }, + deserialized_example("link/ensure-02.janet") + ); + } + + #[test] + fn test_deserialize_link_remove_01() { + assert_eq!( + GurpLinkRemove { + id: "/NO-ROLE/link/_dont_want_this_link".to_owned(), + path: Utf8PathBuf::from("/dont/want/this/link"), + }, + deserialized_example("link/remove-01.janet") + ); + } + + #[test] + fn test_symlink_create() { + let temp_dir = Utf8TempDir::new().unwrap(); + let source_file = temp_dir.child("source-file"); + source_file.write_str("some-content").unwrap(); + let source_path = temp_dir.child("source-file"); + let target_path = temp_dir.child("target"); + + let json_def = janet2json(&indoc::formatdoc! { r#" + (link/ensure "{}" + :source "{}") + "#, + target_path.as_path(), + source_path.as_path(), + }); + + assert!(!target_path.exists()); + let sut: GurpLinkEnsure = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_ONE_CHANGE, sut.apply(&defopts()).unwrap()); + assert!(target_path.exists()); + assert!(target_path.is_symlink()); + } + + #[test] + fn test_symlink_create_noop() { + let temp_dir = Utf8TempDir::new().unwrap(); + let source_file = temp_dir.child("source-file"); + source_file.write_str("some-content").unwrap(); + let source_path = temp_dir.child("source-file"); + let target_path = temp_dir.child("target"); + + let json_def = janet2json(&indoc::formatdoc! { r#" + (link/ensure "{}" + :source "{}") + "#, + target_path.as_path(), + source_path.as_path(), + }); + + assert!(!target_path.exists()); + let sut: GurpLinkEnsure = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_NOOP, sut.apply(&defopts_noop()).unwrap()); + assert!(!target_path.exists()); + } + + #[test] + fn test_symlink_remove() { + let temp = Utf8TempDir::new().unwrap(); + let source = temp.child("source"); + let target = temp.child("target"); + source.write_str("some-content").unwrap(); + unix::fs::symlink(source, &target).unwrap(); + let json_def = janet2json(&format!(r#"(link/remove "{}")"#, target.as_path())); + assert!(target.exists()); + let sut: GurpLinkRemove = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_ONE_CHANGE, sut.apply(&defopts()).unwrap()); + assert!(!target.exists()); + } + + #[test] + fn test_symlink_remove_noop() { + let temp = Utf8TempDir::new().unwrap(); + let source = temp.child("source"); + let target = temp.child("target"); + source.write_str("some-content").unwrap(); + unix::fs::symlink(source, &target).unwrap(); + let json_def = janet2json(&format!(r#"(link/remove "{}")"#, target.as_path())); + assert!(target.exists()); + let sut: GurpLinkRemove = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_NOOP, sut.apply(&defopts_noop()).unwrap()); + assert!(target.exists()); + } + + #[test] + fn test_symlink_remove_missing() { + let json_def = janet2json(r#"(link/remove "/no/such/file")"#); + let sut: GurpLinkRemove = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_NO_CHANGE, sut.apply(&defopts_noop()).unwrap()); + } + + #[test] + fn test_hardlink_create() { + let temp_dir = Utf8TempDir::new().unwrap(); + let source_file = temp_dir.child("source-file"); + source_file.write_str("some-content").unwrap(); + let source_path = temp_dir.child("source-file"); + let target_path = temp_dir.child("target"); + + let json_def = janet2json(&indoc::formatdoc! { r#" + (link/ensure "{}" + :source "{}" + :type "hard")"#, + target_path.as_path(), + source_path.as_path(), + }); + + assert!(!target_path.exists()); + let sut: GurpLinkEnsure = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_ONE_CHANGE, sut.apply(&defopts()).unwrap()); + assert!(target_path.exists()); + use std::os::unix::fs::MetadataExt; + let source_meta = std::fs::metadata(source_path.as_path()).unwrap(); + let target_meta = std::fs::metadata(target_path.as_path()).unwrap(); + assert_eq!(source_meta.ino(), target_meta.ino()); + assert_eq!(source_meta.dev(), target_meta.dev()); + } + + #[test] + fn test_hardlink_no_change() { + let temp_dir = Utf8TempDir::new().unwrap(); + let source_file = temp_dir.child("source-file"); + source_file.write_str("some-content").unwrap(); + let source_path = temp_dir.child("source-file"); + let target_path = temp_dir.child("target"); + std::fs::hard_link(source_path.as_path(), target_path.as_path()).unwrap(); + + let json_def = janet2json(&indoc::formatdoc! { r#" + (link/ensure "{}" + :source "{}" + :type "hard")"#, + target_path.as_path(), + source_path.as_path(), + }); + + let sut: GurpLinkEnsure = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_NO_CHANGE, sut.apply(&defopts()).unwrap()); + + use std::os::unix::fs::MetadataExt; + let source_meta = std::fs::metadata(source_path.as_path()).unwrap(); + let target_meta = std::fs::metadata(target_path.as_path()).unwrap(); + assert_eq!(source_meta.ino(), target_meta.ino()); + } + + #[test] + fn test_hardlink_correction() { + let temp_dir = Utf8TempDir::new().unwrap(); + let old_source_file = temp_dir.child("old-source"); + old_source_file.write_str("old-content").unwrap(); + let old_source_path = temp_dir.child("old-source"); + let new_source_file = temp_dir.child("new-source"); + new_source_file.write_str("new-content").unwrap(); + let new_source_path = temp_dir.child("new-source"); + let target_path = temp_dir.child("target"); + + std::fs::hard_link(old_source_path.as_path(), target_path.as_path()).unwrap(); + + use std::os::unix::fs::MetadataExt; + let old_meta = std::fs::metadata(old_source_path.as_path()).unwrap(); + let target_meta_before = std::fs::metadata(target_path.as_path()).unwrap(); + assert_eq!(old_meta.ino(), target_meta_before.ino()); + + let json_def = janet2json(&indoc::formatdoc! { r#" + (link/ensure "{}" + :source "{}" + :type "hard")"#, + target_path.as_path(), + new_source_path.as_path(), + }); + + let sut: GurpLinkEnsure = serde_json::from_str(&json_def).unwrap(); + assert_eq!(ONE_RESOURCE_ONE_CHANGE, sut.apply(&defopts()).unwrap()); + + let new_meta = std::fs::metadata(new_source_path.as_path()).unwrap(); + let target_meta_after = std::fs::metadata(target_path.as_path()).unwrap(); + assert_eq!(new_meta.ino(), target_meta_after.ino()); + assert_ne!(old_meta.ino(), target_meta_after.ino()); + assert_eq!( + fs::read_to_string(target_path.as_path()).unwrap(), + "new-content".to_owned() + ); + } +} diff --git a/doers/src/symlink.rs b/doers/src/symlink.rs deleted file mode 100644 index 8509f73c..00000000 --- a/doers/src/symlink.rs +++ /dev/null @@ -1,190 +0,0 @@ -use anyhow::{bail, ensure}; -use camino::Utf8PathBuf; -use common::constants::{ONE_RESOURCE_NO_CHANGE, ONE_RESOURCE_ONE_CHANGE}; -use common::types::{ApplyOpts, ApplySummary}; -use serde::Deserialize; -use std::fmt::Debug; -use std::fs; -use std::os::unix; - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct GurpSymlinkEnsure { - #[serde(rename = "_id")] - pub id: String, - #[serde(rename = "name")] - pub path: Utf8PathBuf, - pub source: Utf8PathBuf, -} - -#[derive(Deserialize, Debug, PartialEq, Eq)] -pub struct GurpSymlinkRemove { - #[serde(rename = "_id")] - pub id: String, - #[serde(rename = "name")] - pub path: Utf8PathBuf, -} - -impl GurpSymlinkEnsure { - pub fn apply(&self, opts: &ApplyOpts) -> anyhow::Result { - let target = &self.path; - let source = &self.source; - - ensure!(source.exists(), "source not found: {source}"); - - if !target.exists() { - tracing::info!("creating symlink: {} -> {}", target, source); - return_if_noop!(opts); - - unix::fs::symlink(source, target)?; - Ok(ONE_RESOURCE_ONE_CHANGE) - } else if target.is_symlink() { - let current_source = target.read_link_utf8()?; - if current_source == *source { - tracing::debug!("no change: {}", self.path); - Ok(ONE_RESOURCE_NO_CHANGE) - } else { - tracing::info!( - "change symlink source: [{}] {} -> {}", - target, - ¤t_source, - source - ); - return_if_noop!(opts); - - fs::remove_file(target)?; - unix::fs::symlink(source, target)?; - Ok(ONE_RESOURCE_ONE_CHANGE) - } - } else { - bail!("{} exists and is not a symlink", &target); - } - } -} - -impl GurpSymlinkRemove { - pub fn apply(&self, opts: &ApplyOpts) -> anyhow::Result { - if self.path.exists() { - tracing::info!("removing symlink: {}", self.path); - return_if_noop!(opts); - - fs::remove_file(&self.path)?; - Ok(ONE_RESOURCE_ONE_CHANGE) - } else { - tracing::debug!("not present: {}", self.path); - Ok(ONE_RESOURCE_NO_CHANGE) - } - } -} - -#[cfg(test)] -mod test { - use super::*; - use camino_tempfile_ext::prelude::*; - use common::constants::ONE_RESOURCE_NOOP; - use pretty_assertions::assert_eq; - use std::os::unix; - use tester::{defopts, defopts_noop, deserialized_example, janet2json}; - - #[test] - fn test_deserialize_symlink_ensure_01() { - assert_eq!( - GurpSymlinkEnsure { - id: "/NO-ROLE/symlink/example-link".to_owned(), - path: Utf8PathBuf::from("/link/is/here"), - source: Utf8PathBuf::from("/link/points/here"), - }, - deserialized_example("symlink/ensure-01.janet") - ); - } - - #[test] - fn test_deserialize_symlink_remove_01() { - assert_eq!( - GurpSymlinkRemove { - id: "/NO-ROLE/symlink/_dont_want_this_link".to_owned(), - path: Utf8PathBuf::from("/dont/want/this/link"), - }, - deserialized_example("symlink/remove-01.janet") - ); - } - - #[test] - fn test_symlink_create() { - let temp_dir = Utf8TempDir::new().unwrap(); - let source_file = temp_dir.child("source-file"); - source_file.write_str("some-content").unwrap(); - let source_path = temp_dir.child("source-file"); - let target_path = temp_dir.child("target"); - - let json_def = janet2json(&indoc::formatdoc! { r#" - (symlink/ensure "{}" - :source "{}") - "#, - target_path.as_path(), - source_path.as_path(), - }); - - assert!(!target_path.exists()); - let sut: GurpSymlinkEnsure = serde_json::from_str(&json_def).unwrap(); - assert_eq!(ONE_RESOURCE_ONE_CHANGE, sut.apply(&defopts()).unwrap()); - assert!(target_path.exists()); - assert!(target_path.is_symlink()); - } - - #[test] - fn test_symlink_create_noop() { - let temp_dir = Utf8TempDir::new().unwrap(); - let source_file = temp_dir.child("source-file"); - source_file.write_str("some-content").unwrap(); - let source_path = temp_dir.child("source-file"); - let target_path = temp_dir.child("target"); - - let json_def = janet2json(&indoc::formatdoc! { r#" - (symlink/ensure "{}" - :source "{}") - "#, - target_path.as_path(), - source_path.as_path(), - }); - - assert!(!target_path.exists()); - let sut: GurpSymlinkEnsure = serde_json::from_str(&json_def).unwrap(); - assert_eq!(ONE_RESOURCE_NOOP, sut.apply(&defopts_noop()).unwrap()); - assert!(!target_path.exists()); - } - - #[test] - fn test_symlink_remove() { - let temp = Utf8TempDir::new().unwrap(); - let source = temp.child("source"); - let target = temp.child("target"); - source.write_str("some-content").unwrap(); - unix::fs::symlink(source, &target).unwrap(); - let json_def = janet2json(&format!(r#"(symlink/remove "{}")"#, target.as_path())); - assert!(target.exists()); - let sut: GurpSymlinkRemove = serde_json::from_str(&json_def).unwrap(); - assert_eq!(ONE_RESOURCE_ONE_CHANGE, sut.apply(&defopts()).unwrap()); - assert!(!target.exists()); - } - - #[test] - fn test_symlink_remove_noop() { - let temp = Utf8TempDir::new().unwrap(); - let source = temp.child("source"); - let target = temp.child("target"); - source.write_str("some-content").unwrap(); - unix::fs::symlink(source, &target).unwrap(); - let json_def = janet2json(&format!(r#"(symlink/remove "{}")"#, target.as_path())); - assert!(target.exists()); - let sut: GurpSymlinkRemove = serde_json::from_str(&json_def).unwrap(); - assert_eq!(ONE_RESOURCE_NOOP, sut.apply(&defopts_noop()).unwrap()); - assert!(target.exists()); - } - - #[test] - fn test_symlink_remove_missing() { - let json_def = janet2json(r#"(symlink/remove "/no/such/file")"#); - let sut: GurpSymlinkRemove = serde_json::from_str(&json_def).unwrap(); - assert_eq!(ONE_RESOURCE_NO_CHANGE, sut.apply(&defopts_noop()).unwrap()); - } -} diff --git a/doers/src/types.rs b/doers/src/types.rs index e12d0767..50455ed1 100644 --- a/doers/src/types.rs +++ b/doers/src/types.rs @@ -12,6 +12,7 @@ use crate::ip_interface::{GurpIpInterfaceEnsure, GurpIpInterfaceRemove}; use crate::ip_properties::GurpIpPropertiesEnsure; use crate::ipfilter::{GurpIpfilterEnsure, GurpIpfilterRemove}; use crate::ipnat::{GurpIpnatEnsure, GurpIpnatRemove}; +use crate::link::{GurpLinkEnsure, GurpLinkRemove}; use crate::misc::GurpMiscEnsure; use crate::network_flow::{GurpNetworkFlowEnsure, GurpNetworkFlowRemove}; use crate::pkg::{GurpPkgEnsure, GurpPkgRemove}; @@ -21,7 +22,6 @@ use crate::route::{GurpRouteEnsure, GurpRouteRemove}; use crate::smf::{GurpSmfEnsure, GurpSmfRemove}; use crate::svc::GurpSvcEnsure; use crate::svcprop::{GurpSvcpropEnsure, GurpSvcpropRemove}; -use crate::symlink::{GurpSymlinkEnsure, GurpSymlinkRemove}; use crate::user::{GurpUserEnsure, GurpUserRemove}; use crate::vlan::{GurpVlanEnsure, GurpVlanRemove}; use crate::vnic::{GurpVnicEnsure, GurpVnicRemove}; @@ -86,6 +86,8 @@ pub struct EnsureResources { #[serde(default)] pub ipnat: Vec, #[serde(default)] + pub link: Vec, + #[serde(default)] pub misc: Vec, #[serde(default)] pub network_flow: Vec, @@ -104,8 +106,6 @@ pub struct EnsureResources { #[serde(default)] pub svc: Vec, #[serde(default)] - pub symlink: Vec, - #[serde(default)] pub user: Vec, #[serde(default)] pub vlan: Vec, @@ -147,6 +147,8 @@ pub struct RemoveResources { #[serde(default)] pub ipnat: Vec, #[serde(default)] + pub link: Vec, + #[serde(default)] pub network_flow: Vec, #[serde(default)] pub pkg: Vec, @@ -161,8 +163,6 @@ pub struct RemoveResources { #[serde(default)] pub svcprop: Vec, #[serde(default)] - pub symlink: Vec, - #[serde(default)] pub user: Vec, #[serde(default)] pub vlan: Vec, @@ -288,12 +288,12 @@ impl Applicator { apply_resources!(summary_total, changed_ids, &ensure.directory, opts); apply_resources!(summary_total, changed_ids, &ensure.file, opts); apply_resources!(summary_total, changed_ids, &ensure.file_line, opts); - apply_resources!(summary_total, changed_ids, &ensure.symlink, opts); + apply_resources!(summary_total, changed_ids, &ensure.link, opts); apply_resources!(summary_total, changed_ids, &ensure.svcprop, opts); apply_resources!(summary_total, changed_ids, &ensure.smf, opts); apply_resources!(summary_total, changed_ids, &ensure.misc, opts); - apply_resources!(summary_total, changed_ids, &remove.symlink, opts); + apply_resources!(summary_total, changed_ids, &remove.link, opts); apply_resources!(summary_total, changed_ids, &remove.file_line, opts); apply_resources!(summary_total, changed_ids, &remove.file, opts); apply_resources!(summary_total, changed_ids, &remove.directory, opts); diff --git a/embed/Cargo.toml b/embed/Cargo.toml index 0504cde5..6e8afe3d 100644 --- a/embed/Cargo.toml +++ b/embed/Cargo.toml @@ -16,3 +16,4 @@ util = { path = "../util" } [build-dependencies] build_helper = { path = "../build_helper" } +walkdir = "2.5.0" diff --git a/embed/build.rs b/embed/build.rs index 53462c85..4612b849 100644 --- a/embed/build.rs +++ b/embed/build.rs @@ -2,7 +2,10 @@ // source. fn main() { - println!("cargo:rerun-if-changed=../janet/src"); + for entry in walkdir::WalkDir::new("../janet/src") { + let entry = entry.unwrap(); + println!("cargo:rerun-if-changed={}", entry.path().display()); + } build_helper::ImageHelper::new(vec!["gurp.janet", "command-lib.janet"], "gurp.jimage") .compile_to_file(); diff --git a/janet/examples/link/ensure-01.janet b/janet/examples/link/ensure-01.janet new file mode 100644 index 00000000..f713ab20 --- /dev/null +++ b/janet/examples/link/ensure-01.janet @@ -0,0 +1,3 @@ +(link/ensure "/symlink/is/here" + :label "example-symlink" + :source "/link/points/here") diff --git a/janet/examples/link/ensure-02.janet b/janet/examples/link/ensure-02.janet new file mode 100644 index 00000000..110565cb --- /dev/null +++ b/janet/examples/link/ensure-02.janet @@ -0,0 +1,3 @@ +(link/ensure "/link/is/here" + :type "hard" + :source "/link/points/here") diff --git a/janet/examples/link/remove-01.janet b/janet/examples/link/remove-01.janet new file mode 100644 index 00000000..d381c834 --- /dev/null +++ b/janet/examples/link/remove-01.janet @@ -0,0 +1 @@ +(link/remove "/dont/want/this/link") diff --git a/janet/examples/symlink/ensure-01.janet b/janet/examples/symlink/ensure-01.janet deleted file mode 100644 index 3135999f..00000000 --- a/janet/examples/symlink/ensure-01.janet +++ /dev/null @@ -1,3 +0,0 @@ -(symlink/ensure "/link/is/here" - :label "example-link" - :source "/link/points/here") diff --git a/janet/examples/symlink/remove-01.janet b/janet/examples/symlink/remove-01.janet deleted file mode 100644 index 46ed9e8f..00000000 --- a/janet/examples/symlink/remove-01.janet +++ /dev/null @@ -1 +0,0 @@ -(symlink/remove "/dont/want/this/link") diff --git a/janet/lib/.gitkeep b/janet/lib/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/janet/lib/README.md b/janet/lib/README.md deleted file mode 100644 index e0372548..00000000 --- a/janet/lib/README.md +++ /dev/null @@ -1 +0,0 @@ -Target directory for library file. Normally compiled by embed/build.rs. diff --git a/janet/src/doers.janet b/janet/src/doers.janet index 669b0a32..186b51f9 100644 --- a/janet/src/doers.janet +++ b/janet/src/doers.janet @@ -15,6 +15,7 @@ (import ./doers/ipfilter :export true) (import ./doers/ipnat :export true) (import ./doers/lib :export true) +(import ./doers/link :export true) (import ./doers/misc :export true) (import ./doers/network-flow :export true) (import ./doers/pkg :export true) @@ -24,7 +25,6 @@ (import ./doers/smf :export true) (import ./doers/svc :export true) (import ./doers/svcprop :export true) -(import ./doers/symlink :export true) (import ./doers/user :export true) (import ./doers/vlan :export true) (import ./doers/vnic :export true) diff --git a/janet/src/doers/file-line.janet b/janet/src/doers/file-line.janet index d5ee8f24..7f80d91e 100644 --- a/janet/src/doers/file-line.janet +++ b/janet/src/doers/file-line.janet @@ -44,16 +44,18 @@ [name & spec] (def spec-struct (make-spec-struct ;spec)) - (if-let [match-val (spec-struct :match)] - (if-not (has-value? match-allowed match-val) - (error - (string "match must be one of " - (comma-sep match-allowed) " [Got '" match-val "']")))) + (pinpoint-error + "remove" + (if-let [match-val (spec-struct :match)] + (if-not (has-value? match-allowed match-val) + (error + (string "match must be one of " + (comma-sep match-allowed) " [Got '" match-val "']")))) - (if-let [type-val (spec-struct :apply-to)] - (if-not (has-value? apply-to-allowed type-val) - (error - (string "type must be one of " (comma-sep apply-to-allowed))))) + (if-let [type-val (spec-struct :apply-to)] + (if-not (has-value? apply-to-allowed type-val) + (error + (string "type must be one of " (comma-sep apply-to-allowed)))))) (def all-specs (spec-with-defaults defaults-remove spec-struct)) (def safe-specs (checked-spec all-specs diff --git a/janet/src/doers/file.janet b/janet/src/doers/file.janet index 35cb8623..3bacc850 100644 --- a/janet/src/doers/file.janet +++ b/janet/src/doers/file.janet @@ -64,9 +64,12 @@ (set (spec-table :from) url-or-qualified-path)))) (def all-specs (spec-with-defaults defaults-ensure spec-table)) - (def safe-specs (checked-spec all-specs - mandatory-props-ensure - optional-props-ensure)) + (def safe-specs + (pinpoint-error + :ensure + (checked-spec all-specs + mandatory-props-ensure + optional-props-ensure))) (collector/push :ensure doer (spec->resource doer name safe-specs))) diff --git a/janet/src/doers/ip-interface.janet b/janet/src/doers/ip-interface.janet index 9abf7797..cf232481 100644 --- a/janet/src/doers/ip-interface.janet +++ b/janet/src/doers/ip-interface.janet @@ -17,7 +17,9 @@ [name & spec] (def spec-table - (group-ip-properties mandatory-props-ensure optional-props-ensure ;spec)) + (pinpoint-error + :ensure + (group-ip-properties mandatory-props-ensure optional-props-ensure ;spec))) (collector/push :ensure doer (spec->resource doer name spec-table))) diff --git a/janet/src/doers/ip-properties.janet b/janet/src/doers/ip-properties.janet index 93608c21..607e1287 100644 --- a/janet/src/doers/ip-properties.janet +++ b/janet/src/doers/ip-properties.janet @@ -13,6 +13,8 @@ [name & spec] (def spec-table - (group-ip-properties mandatory-props-ensure optional-props-ensure ;spec)) + (pinpoint-error + :ensure + (group-ip-properties mandatory-props-ensure optional-props-ensure ;spec))) (collector/push :ensure doer (spec->resource doer name spec-table))) diff --git a/janet/src/doers/ipfilter.janet b/janet/src/doers/ipfilter.janet index c75793d1..d0fa236e 100644 --- a/janet/src/doers/ipfilter.janet +++ b/janet/src/doers/ipfilter.janet @@ -25,7 +25,9 @@ "Given rules or a path to a rules file, put an ensure struct in the collector" [name & spec] (if-not (has-exactly-one-of? [:content :from] spec) - (error "need exactly one of :content or :from")) + (pinpoint-error + :ensure + (error "need exactly one of :content or :from"))) (collector/push :ensure doer (make-ensure-resource))) diff --git a/janet/src/doers/ipnat.janet b/janet/src/doers/ipnat.janet index 2779be89..75db042a 100644 --- a/janet/src/doers/ipnat.janet +++ b/janet/src/doers/ipnat.janet @@ -21,7 +21,9 @@ "Given rules or a path to a rules file, put an ensure struct in the collector" [name & spec] (if-not (has-exactly-one-of? [:content :from] spec) - (error "need exactly one of :content or :from")) + (pinpoint-error + :ensure + (error "need exactly one of :content or :from"))) (collector/push :ensure doer (make-ensure-resource))) diff --git a/janet/src/doers/lib.janet b/janet/src/doers/lib.janet index f3980036..53a029ef 100644 --- a/janet/src/doers/lib.janet +++ b/janet/src/doers/lib.janet @@ -8,7 +8,8 @@ (def protocol-opts (tabseq [protocol :in ip-protocols] protocol {:types [:struct :table] - :help (string "key-value pairs of valid " protocol " properties")})) + :help (string/format "key-value pairs of valid %s properties" + protocol)})) (defn comma-sep "Return a comma-separated string of the items in list" @@ -16,8 +17,8 @@ (string/join (map |(string/format "%p" $) list) ", ")) (defn check-key-type - "Checks something is of a permissible type. Raises an error if it is not. Values - can *always* be keywords, because they denote references." + "Checks something is of a permissible type. Raises an error if it is not. + Values can *always* be keywords, because they denote references." [prop-name prop-value allowed-types] (def prop-type (type prop-value)) @@ -36,12 +37,13 @@ (struct ;spec) ([e] (error - (string/format "unable to create struct from %d arg(s): %p: %s" (length spec) spec e))))) + (string/format "unable to create struct from %d arg(s): %p: %s" + (length spec) spec e))))) (defn checked-spec "Compares a user's spec against what a resource definition expects. Raises - an error if anything is not as it should be, otherwise the given spec as a - struct." + an error if anything is not as it should be, otherwise returns the given spec + as a struct." [spec-struct mandatory-props optional-props] (def optional-props @@ -67,7 +69,10 @@ (string (string/format "unexpected property %p. Valid properties are " prop-name) - (comma-sep (array/concat @[] (keys mandatory-props) (keys optional-props)))))))) + (comma-sep + (array/concat @[] + (keys mandatory-props) + (keys optional-props)))))))) spec-struct) @@ -97,29 +102,44 @@ [default-prop-values spec-struct] (merge default-prop-values spec-struct)) +(defmacro pinpoint-error + "Wraps an error in a string describing the resource which caused it" + [action & body] + (with-syms [$e] + ~(try + (do + ,;body) + ([$e] + (error + (string/format "In %s/%s %s: %s" doer ,action name $e)))))) + (defmacro make-ensure-resource "Pulls together some boilerplate in doer ensure functions" [] - ~(do - (def spec-struct (make-spec-struct ;spec)) - (def all-specs (spec-with-defaults defaults-ensure spec-struct)) - (def safe-specs (checked-spec all-specs - mandatory-props-ensure - optional-props-ensure)) + (with-syms [$e] + ~(pinpoint-error + :ensure + (def spec-struct (make-spec-struct ;spec)) + (def all-specs (spec-with-defaults defaults-ensure spec-struct)) + (def safe-specs (checked-spec all-specs + mandatory-props-ensure + optional-props-ensure)) - (spec->resource doer name safe-specs))) + (spec->resource doer name safe-specs)))) (defmacro make-remove-resource "Pulls together some boilerplate in doer remove functions" [] - ~(do - (def spec-struct (make-spec-struct ;spec)) - (def all-specs (spec-with-defaults defaults-remove spec-struct)) - (def safe-specs (checked-spec all-specs - mandatory-props-remove - optional-props-remove)) + (with-syms [$e] + ~(pinpoint-error + :remove + (def spec-struct (make-spec-struct ;spec)) + (def all-specs (spec-with-defaults defaults-remove spec-struct)) + (def safe-specs (checked-spec all-specs + mandatory-props-remove + optional-props-remove)) - (spec->resource doer name safe-specs))) + (spec->resource doer name safe-specs)))) (defn has-exactly-one-of? "Checks whether a spec contains exactly one of the required-keys" @@ -167,7 +187,7 @@ (defn group-ip-properties "Move IP protocol properties into a separate :protocol property" [mandatory-props optional-props & spec] - + (def temp-spec-table (checked-spec (make-spec-struct ;spec) mandatory-props optional-props)) diff --git a/janet/src/doers/symlink.janet b/janet/src/doers/link.janet similarity index 53% rename from janet/src/doers/symlink.janet rename to janet/src/doers/link.janet index 97f4fa81..0f95bded 100644 --- a/janet/src/doers/symlink.janet +++ b/janet/src/doers/link.janet @@ -1,30 +1,34 @@ (use ./lib) (import ../collector) -(def doer :symlink) -(def description "Create and remove symbolic links.") +(def doer :link) +(def description "Create and remove links.") (def name-is "Qualified path to the link that will be created") (def mandatory-props-ensure {:source {:types [:string] - :help "The file the symlink points to"}}) + :help "The file to which we will link"} + :type {:types [:string] + :help "The type of link: symbolic or hard"}}) (def optional-props-ensure {}) (def mandatory-props-remove {}) (def optional-props-remove {}) -(def defaults-ensure {}) +(def defaults-ensure + {:type "symbolic"}) (def defaults-remove {}) (defn ensure - "Given a symlink path and target, put an ensure struct in the collector" + "Given target and source paths, put an ensure struct in the collector" [name & spec] (collector/push :ensure doer (make-ensure-resource))) (defn remove - "Given a symlink path, put a remove struct in the collector" + "Given target and source paths, put a remove struct in the collector" [name & spec] (collector/push :remove doer (make-remove-resource))) (def notes - ["If the :source doesn't exist, you get an error." - "Files are ensured before links, so you can make a file and link to it." + ["If the source doesn't exist, you get an error." + "Files and directories are ensured before links, so you can link Gurp-managed + resources." "If the link exists and points to the wrong file, it will be removed and re-created, and if it exists but is not a link, that's an error."]) diff --git a/janet/src/doers/smf.janet b/janet/src/doers/smf.janet index 348a8d8e..f961907e 100644 --- a/janet/src/doers/smf.janet +++ b/janet/src/doers/smf.janet @@ -70,7 +70,14 @@ (expand-resource :refresh-method :as-struct true) (def modified-spec-struct (make-spec-struct ;modified-spec)) - (def spec-struct (checked-spec modified-spec-struct mandatory-props-ensure optional-props-ensure)) + + (def spec-struct + (pinpoint-error :ensure + (checked-spec + modified-spec-struct + mandatory-props-ensure + optional-props-ensure))) + (def spec-table (spec-with-defaults defaults-ensure spec-struct)) # Properties must be expanded diff --git a/janet/src/doers/smf/dependency.janet b/janet/src/doers/smf/dependency.janet index e0035a04..95ec8062 100644 --- a/janet/src/doers/smf/dependency.janet +++ b/janet/src/doers/smf/dependency.janet @@ -24,7 +24,15 @@ (defn dependency "A convenience function to help produce an SMF dependency" [name & spec] - (def spec-struct (checked-spec (make-spec-struct :name name ;spec) mandatory-props-dependency optional-props-dependency)) + (def spec-struct + (do + (def doer "smf") + (pinpoint-error + :dependency + (checked-spec (make-spec-struct :name name ;spec) + mandatory-props-dependency + optional-props-dependency)))) + (def all-specs (spec-with-defaults defaults-dependency spec-struct)) (struct :dependencies all-specs)) diff --git a/janet/src/doers/smf/dependent.janet b/janet/src/doers/smf/dependent.janet index f2fa48d9..7e60c517 100644 --- a/janet/src/doers/smf/dependent.janet +++ b/janet/src/doers/smf/dependent.janet @@ -25,9 +25,13 @@ "A convenience function to help produce an SMF dependent" [name & spec] (def spec-struct - (checked-spec - (make-spec-struct :name name ;spec) - mandatory-props-dependent optional-props-dependent)) + (do + (def doer "smf") + (pinpoint-error + :dependent + (checked-spec (make-spec-struct :name name ;spec) + mandatory-props-dependent + optional-props-dependent)))) (def all-specs (spec-with-defaults defaults-dependent spec-struct)) (struct :dependencies all-specs)) diff --git a/janet/src/doers/smf/method.janet b/janet/src/doers/smf/method.janet index ba32dc58..99cb697e 100644 --- a/janet/src/doers/smf/method.janet +++ b/janet/src/doers/smf/method.janet @@ -4,7 +4,7 @@ (def context-props [:user :group :privileges :environment]) (def description-method "Defines an SMF method to launch a service state") -(def name-is-method (string "One of " (comma-sep allowed-methods))) +(def name-is-method (string "One of " (comma-sep allowed-methods))) (def optional-props-method {:user {:types [:string] @@ -28,30 +28,39 @@ (defn method "Produce an SMF exec_method, with a context" [name & spec] - (if-not (has-value? allowed-methods name) - (error - (string "smf/method name must be one of " (comma-sep allowed-methods)))) - (def spec-table (spec-with-defaults defaults-method (make-spec-struct ;spec))) + (let [doer "smf"] + (pinpoint-error + :method - # We have to move context related properties (context-props) into a - # :context struct + (if-not (has-value? allowed-methods name) + (error + (string "smf/method name must be one of " (comma-sep allowed-methods)))) - (var context-table @{}) + (def spec-table + (checked-spec + (spec-with-defaults defaults-method (make-spec-struct ;spec)) + mandatory-props-method + optional-props-method)) - (loop [prop :in context-props] - (when-let [spec-value (get spec-table prop)] - (def value-to-move (if (= prop :privileges) - (string/join spec-value ",") - spec-value)) + # We have to move context related properties (context-props) into a + # :context struct - (set (context-table prop) value-to-move) - (set (spec-table prop) nil))) + (var context-table @{}) - (if-not (empty? context-table) - (set (spec-table :context) (table/to-struct context-table))) + (loop [prop :in context-props] + (when-let [spec-value (get spec-table prop)] + (def value-to-move (if (= prop :privileges) + (string/join spec-value ",") + spec-value)) - (struct (keyword (string name "-method")) spec-table)) + (set (context-table prop) value-to-move) + (set (spec-table prop) nil))) + + (if-not (empty? context-table) + (set (spec-table :context) (table/to-struct context-table))) + + (struct (keyword (string name "-method")) spec-table)))) (def notes-method ["If you don't supply a `:stop-method` you get a standard `:kill` that times diff --git a/janet/src/doers/svc.janet b/janet/src/doers/svc.janet index b26b5502..5f9202d3 100644 --- a/janet/src/doers/svc.janet +++ b/janet/src/doers/svc.janet @@ -16,7 +16,7 @@ {:types [:array] :help "Labels of resources whose alteration triggers service restart"}}) (def defaults-ensure - { :restarted-by [] + {:restarted-by [] :reloaded-by []}) (defn ensure @@ -32,9 +32,11 @@ (if-let [reloaders (spec-table :reloaded-by)] (set (spec-table :reloaded-by) (map string reloaders))) - (def safe-specs (checked-spec spec-table - mandatory-props-ensure - optional-props-ensure)) + (def safe-specs + (pinpoint-error + :ensure (checked-spec spec-table + mandatory-props-ensure + optional-props-ensure))) (collector/push :ensure doer (spec->resource doer name safe-specs))) diff --git a/janet/src/doers/svcprop.janet b/janet/src/doers/svcprop.janet index 2f29acaf..d83b80b3 100644 --- a/janet/src/doers/svcprop.janet +++ b/janet/src/doers/svcprop.janet @@ -28,9 +28,11 @@ "Given a service property spec, put an ensure struct in the collector" [name & spec] (def spec-struct - (checked-spec (make-spec-struct ;spec) - mandatory-props-ensure - optional-props-ensure)) + (pinpoint-error + :ensure + (checked-spec (make-spec-struct ;spec) + mandatory-props-ensure + optional-props-ensure))) (def spec-table (spec-with-defaults defaults-ensure spec-struct)) # Properties must be expanded diff --git a/janet/src/doers/zone.janet b/janet/src/doers/zone.janet index acd10e16..d8d55df0 100644 --- a/janet/src/doers/zone.janet +++ b/janet/src/doers/zone.janet @@ -104,7 +104,15 @@ (expand-resource :bootstrap :as-struct true) (def modified-spec-struct (make-spec-struct ;modified-spec)) - (def spec-struct (checked-spec modified-spec-struct mandatory-props-ensure optional-props-ensure)) + + (def spec-struct + (pinpoint-error + :ensure + (checked-spec + modified-spec-struct + mandatory-props-ensure + optional-props-ensure))) + (def spec-table (spec-with-defaults defaults-ensure spec-struct)) (if-let [copy-resource (get spec-table :copy-in)] diff --git a/janet/src/doers/zone/attr.janet b/janet/src/doers/zone/attr.janet index 78332568..f73cc862 100644 --- a/janet/src/doers/zone/attr.janet +++ b/janet/src/doers/zone/attr.janet @@ -1,5 +1,6 @@ (use ../lib) +(def doer :zone) (def description-attr "Set attributes on a zone being created by the zone doer.") (def name-is-attr "Attribute name") (def optional-props-attr @@ -18,11 +19,14 @@ (defn attr "Given a spec, return a zone attr struct." [name & spec] - (def spec-struct (make-spec-struct :name name ;spec)) - (def spec-table (checked-spec (spec-with-defaults defaults-attr spec-struct) - mandatory-props-attr - optional-props-attr)) + (def expanded-spec (spec-with-defaults defaults-attr spec-struct)) + (def spec-table + (pinpoint-error :attr + (checked-spec + expanded-spec + mandatory-props-attr + optional-props-attr))) (if-not (has-key? spec-table :type) (set (spec-table :type) diff --git a/janet/src/doers/zone/bhyve.janet b/janet/src/doers/zone/bhyve.janet index 9b1dbfad..3134bdd2 100644 --- a/janet/src/doers/zone/bhyve.janet +++ b/janet/src/doers/zone/bhyve.janet @@ -1,5 +1,6 @@ (use ../lib) +(def doer :zone) (def description-bhyve "Describe a bhyve zone inside a zone resource.") (def name-is-bhyve nil) (def defaults-bhyve @@ -40,10 +41,13 @@ (defn bhyve "Given a spec, return config for a bhyve zone" [& spec] + (def name "NO-NAME") (def spec-struct (make-spec-struct ;spec)) - (def spec-table (checked-spec (spec-with-defaults defaults-bhyve spec-struct) - mandatory-props-bhyve - optional-props-bhyve)) + (def expanded-spec (spec-with-defaults defaults-bhyve spec-struct)) + (def spec-table + (pinpoint-error + :bhyve + (checked-spec expanded-spec mandatory-props-bhyve optional-props-bhyve))) (struct :bhyve spec-table)) (def notes-bhyve diff --git a/janet/src/doers/zone/bootstrap.janet b/janet/src/doers/zone/bootstrap.janet index c5632f27..635a634f 100644 --- a/janet/src/doers/zone/bootstrap.janet +++ b/janet/src/doers/zone/bootstrap.janet @@ -1,5 +1,6 @@ (use ../lib) +(def doer :zone) (def description-bootstrap "Tells gurp how to bootstrap a newly created zone.") (def name-is-bootstrap nil) (def mandatory-props-bootstrap {}) @@ -15,10 +16,17 @@ (defn bootstrap "Given a spec, return config to bootstrap a zone" [& spec] + (def name "NO-NAME") (def spec-struct (make-spec-struct ;spec)) - (def spec-table (checked-spec (spec-with-defaults defaults-bootstrap spec-struct) - mandatory-props-bootstrap - optional-props-bootstrap)) + (def expanded-spec (spec-with-defaults defaults-bootstrap spec-struct)) + + (def spec-table + (pinpoint-error :bootstrap + (checked-spec + expanded-spec + mandatory-props-bootstrap + optional-props-bootstrap))) + (struct :bootstrap spec-table)) (def notes-bootstrap diff --git a/janet/src/doers/zone/fs.janet b/janet/src/doers/zone/fs.janet index 8229fa4d..c264c07b 100644 --- a/janet/src/doers/zone/fs.janet +++ b/janet/src/doers/zone/fs.janet @@ -1,5 +1,6 @@ (use ../lib) +(def doer :zone) (def description-fs "Define a filesystem mapping when creating a zone.") (def name-is-fs "The mountpoint inside the zone") (def optional-props-fs @@ -20,7 +21,9 @@ "Given a spec, return a zone fs struct." [name & spec] (def spec-struct (make-spec-struct :dir name ;spec)) - (def spec-table (checked-spec (spec-with-defaults defaults-fs spec-struct) - mandatory-props-fs - optional-props-fs)) + (def expanded-spec (spec-with-defaults defaults-fs spec-struct)) + (def spec-table + (pinpoint-error + :fs + (checked-spec expanded-spec mandatory-props-fs optional-props-fs))) (struct :fs spec-table)) diff --git a/janet/src/doers/zone/network.janet b/janet/src/doers/zone/network.janet index 1dfeb5c4..b5c1adfa 100644 --- a/janet/src/doers/zone/network.janet +++ b/janet/src/doers/zone/network.janet @@ -1,5 +1,6 @@ (use ../lib) +(def doer :zone) (def description-network "Describe network configuration of a zone resource.") (def name-is-network "Zone VNIC, which may already exist") (def mandatory-props-network @@ -16,13 +17,19 @@ :defrouter {:types [:string] :help "IP address of default router"}}) -(def defaults-network {:global-nic "auto"}) +(def defaults-network + {:global-nic "auto"}) (defn network "Given a spec, return a zone network struct." [physical & spec] + (def name "NO-NAME") (def spec-struct (make-spec-struct :physical physical ;spec)) - (def spec-table (checked-spec (spec-with-defaults defaults-network spec-struct) - mandatory-props-network - optional-props-network)) + (def expanded-spec (spec-with-defaults defaults-network spec-struct)) + (def spec-table + (pinpoint-error :network + (checked-spec + expanded-spec + mandatory-props-network + optional-props-network))) (struct :net spec-table)) diff --git a/janet/src/doers/zone/rctl.janet b/janet/src/doers/zone/rctl.janet index f3755ab6..ec455f1b 100644 --- a/janet/src/doers/zone/rctl.janet +++ b/janet/src/doers/zone/rctl.janet @@ -1,5 +1,6 @@ (use ../lib) +(def doer :zone) (def description-rctl "Define a resource control when creating a zone.") (def name-is-rctl "RCTL name") (def mandatory-props-rctl @@ -20,7 +21,9 @@ "Given a spec, return a zone rctl struct." [name & spec] (def spec-struct (make-spec-struct :name name ;spec)) - (def spec-table (checked-spec (spec-with-defaults defaults-rctl spec-struct) - mandatory-props-rctl - optional-props-rctl)) + (def expanded-spec (spec-with-defaults defaults-rctl spec-struct)) + (def spec-table + (pinpoint-error + :rctl + (checked-spec expanded-spec mandatory-props-rctl optional-props-rctl))) (struct :rctl spec-table)) diff --git a/janet/test/commands.janet b/janet/test/commands.janet index 2b3f1a50..028d4ab3 100644 --- a/janet/test/commands.janet +++ b/janet/test/commands.janet @@ -5,7 +5,7 @@ (deftest list-doers (test (strip-ansi (list-doers)) - @" apk Install and uninstall APK packages. Only\n valid in an Alpine LX zone.\n bridge Create and modify ethernet bridges.\n cron Manage cron jobs. Crontab entries are\n prefixed with a machine-generated string.\n directory Create and remove directories. Parents are\n created like mkdir -p, but with the\n owner/group/mode of the gurp process. Removal\n always removes directory contents.\n etherstub Create and destroy etherstubs.\n file-line Ensure lines do or do not exist in the\n given file.\n file Create files from multiple sources, or\n remove them.\n gem Install and uninstall Ruby gems.\n group Create and destroy Unix groups.\n ip-address Manages IP addresses via ipadm.\n ip-interface Create and destroy IP interfaces, with\n optional properties. Properties are supplied with\n 'ip-interface-protocol'.\n ip-properties Sets global IP properties, via 'ipadm\n set-prop'.\n ipfilter Set or remove ipfilter rules.\n ipnat Set or remove NAT rules.\n misc A collection of things too small to deserve\n their own doer.\n network-flow Manage network flows via flowadm.\n pkg Install and uninstall pkg(5) packages.\n pkgin Install and uninstall pkgin packages. Only\n valid in a pkgsrc zone.\n publisher Add and remove pkg(5) publisher origins.\n route Manage routes. Note that default routes for\n zones should be handled by the zone's :defrouter\n property.\n smf Create and install a manifest for an SMF\n service.\n svc Manage the state of an existing SMF\n service.\n svcprop Set and remove properties and property\n groups of an existing SMF service.\n symlink Create and remove symbolic links.\n user Manage Unix users\n vlan Manage VLAN objects\n vnic Manage VNIC objects\n zfs Create, destroy, and modify properties of\n ZFS filesystems.\n zone Create and destroy zones. Existing zones\n cannot be modified.")) + @" apk Install and uninstall APK packages. Only\n valid in an Alpine LX zone.\n bridge Create and modify ethernet bridges.\n cron Manage cron jobs. Crontab entries are\n prefixed with a machine-generated string.\n directory Create and remove directories. Parents are\n created like mkdir -p, but with the\n owner/group/mode of the gurp process. Removal\n always removes directory contents.\n etherstub Create and destroy etherstubs.\n file-line Ensure lines do or do not exist in the\n given file.\n file Create files from multiple sources, or\n remove them.\n gem Install and uninstall Ruby gems.\n group Create and destroy Unix groups.\n ip-address Manages IP addresses via ipadm.\n ip-interface Create and destroy IP interfaces, with\n optional properties. Properties are supplied with\n 'ip-interface-protocol'.\n ip-properties Sets global IP properties, via 'ipadm\n set-prop'.\n ipfilter Set or remove ipfilter rules.\n ipnat Set or remove NAT rules.\n link Create and remove links.\n misc A collection of things too small to deserve\n their own doer.\n network-flow Manage network flows via flowadm.\n pkg Install and uninstall pkg(5) packages.\n pkgin Install and uninstall pkgin packages. Only\n valid in a pkgsrc zone.\n publisher Add and remove pkg(5) publisher origins.\n route Manage routes. Note that default routes for\n zones should be handled by the zone's :defrouter\n property.\n smf Create and install a manifest for an SMF\n service.\n svc Manage the state of an existing SMF\n service.\n svcprop Set and remove properties and property\n groups of an existing SMF service.\n user Manage Unix users\n vlan Manage VLAN objects\n vnic Manage VNIC objects\n zfs Create, destroy, and modify properties of\n ZFS filesystems.\n zone Create and destroy zones. Existing zones\n cannot be modified.")) (deftest help-for-doer (test diff --git a/janet/test/doers/apk.janet b/janet/test/doers/apk.janet index 3270a7ac..9872ff89 100644 --- a/janet/test/doers/apk.janet +++ b/janet/test/doers/apk.janet @@ -20,10 +20,13 @@ :role "test-role"} {:_id "/test-role/apk/python" :name "python" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest apk-error + (test-error + (apk/remove "go" :version "1.20.1") + "In apk/remove go: unexpected property :version. Valid properties are :label") + (test-error (apk/ensure "gurp" :version "1.1.1") - "unexpected property :version. Valid properties are :label")) + "In apk/ensure gurp: unexpected property :version. Valid properties are :label")) diff --git a/janet/test/doers/bridge.janet b/janet/test/doers/bridge.janet index 296ea71a..0fed088a 100644 --- a/janet/test/doers/bridge.janet +++ b/janet/test/doers/bridge.janet @@ -31,12 +31,11 @@ :role "test-role"}]} :remove @{:bridge @[{:_id "/test-role/bridge/unwanted" :name "unwanted" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest bridge-errors (test-error (bridge/ensure "test_d" :oops "wat?") - "unexpected property :oops. Valid properties are :priority, :links, :label, :max-age, :force-protocol, :protect, :forward-delay, :hello-time") + "In bridge/ensure test_d: unexpected property :oops. Valid properties are :priority, :links, :label, :max-age, :force-protocol, :protect, :forward-delay, :hello-time") (test-error (bridge/ensure "test_e" :priority "high!") - "priority is of type :string. Allowed types :number")) + "In bridge/ensure test_e: priority is of type :string. Allowed types :number")) diff --git a/janet/test/doers/cron.janet b/janet/test/doers/cron.janet index aceb5652..3db675d0 100644 --- a/janet/test/doers/cron.janet +++ b/janet/test/doers/cron.janet @@ -11,36 +11,35 @@ (import-tests "cron" (curenv)) (test *collector* - @{:ensure @{:cron @[{:_id "/test-role/cron/mostly-default-values" - :command "/bin/thing arg1 arg2 arg3" - :day-of-month "*" - :day-of-week "*" - :hour "*" - :minute 6 - :month-of-year "*" - :name "mostly-default-values" - :role "test-role" - :user "root"} - {:_id "/test-role/cron/some-cron-job" - :command "/bin/thing arg1 arg2 arg3" - :day-of-month "*" - :day-of-week 5 - :hour 4 - :label "some-cron-job" - :minute 6 - :month-of-year "*" - :name "lots-of-values" - :role "test-role" - :user "test-user"}]} - :remove @{:cron @[{:_id "/test-role/cron/that-old-cron-job" - :name "that-old-cron-job" - :role "test-role" - :user "root"}]}})) + @{:ensure @{:cron @[{:_id "/test-role/cron/mostly-default-values" + :command "/bin/thing arg1 arg2 arg3" + :day-of-month "*" + :day-of-week "*" + :hour "*" + :minute 6 + :month-of-year "*" + :name "mostly-default-values" + :role "test-role" + :user "root"} + {:_id "/test-role/cron/some-cron-job" + :command "/bin/thing arg1 arg2 arg3" + :day-of-month "*" + :day-of-week 5 + :hour 4 + :label "some-cron-job" + :minute 6 + :month-of-year "*" + :name "lots-of-values" + :role "test-role" + :user "test-user"}]} + :remove @{:cron @[{:_id "/test-role/cron/that-old-cron-job" + :name "that-old-cron-job" + :role "test-role" + :user "root"}]}}) -(deftest cron-error (test-error (cron/ensure "missing-data" :hour 6) - "did not find mandatory property :command. Mandatory properties are :command") + "In cron/ensure missing-data: did not find mandatory property :command. Mandatory properties are :command, :user") (test-error (cron/ensure "junk-keys" @@ -49,4 +48,4 @@ :day "monday" :colour "blue" :hour 6) - "unexpected property :colour. Valid properties are :command, :minute, :hour, :month-of-year, :day-of-month, :user, :label, :day-of-week")) + "In cron/ensure junk-keys: unexpected property :colour. Valid properties are :command, :user, :minute, :hour, :month-of-year, :day-of-month, :label, :day-of-week")) diff --git a/janet/test/doers/directory.janet b/janet/test/doers/directory.janet index 83b5f0b3..d5bf75bc 100644 --- a/janet/test/doers/directory.janet +++ b/janet/test/doers/directory.janet @@ -32,12 +32,11 @@ :role "test-role"}]} :remove @{:directory @[{:_id "/test-role/directory/_path_to_dir" :name "/path/to/dir" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest directory-error (test-error (directory/ensure "/extra/keys" :owner "me" :gid 234 :recursive true) - "unexpected property :recursive. Valid properties are :owner, :group, :mode, :label")) + "In directory/ensure /extra/keys: unexpected property :recursive. Valid properties are :owner, :group, :mode, :label")) diff --git a/janet/test/doers/etherstub.janet b/janet/test/doers/etherstub.janet index 5f2323fe..bb84a121 100644 --- a/janet/test/doers/etherstub.janet +++ b/janet/test/doers/etherstub.janet @@ -19,10 +19,9 @@ :role "test-role"}]} :remove @{:etherstub @[{:_id "/test-role/etherstub/oldstub0" :name "oldstub0" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest etherstub-error (test-error (etherstub/ensure "estub4" :with "field") - "unexpected property :with. Valid properties are :label")) + "In etherstub/ensure estub4: unexpected property :with. Valid properties are :label")) diff --git a/janet/test/doers/file-line.janet b/janet/test/doers/file-line.janet index df769d44..38d11b03 100644 --- a/janet/test/doers/file-line.janet +++ b/janet/test/doers/file-line.janet @@ -31,18 +31,17 @@ :match "starts-with" :name "/path/to/file" :pattern "string-prefix" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest file-line-error (test-error (file-line/ensure "/missing/line" :line "and" :after "gibbus" :before "chubb") - "unexpected property :before. Valid properties are :with, :apply-to, :replace, :label, :insert-at, :line") + "In file-line/ensure /missing/line: unexpected property :before. Valid properties are :with, :apply-to, :replace, :label, :insert-at, :line") (test-error (file-line/remove "/my/file" :pattern "merp" :match "end") - "match must be one of \"exact\", \"starts_with\", \"ends_with\", \"contains\", \"matches\" [Got 'end']")) + "In file-line/remove /my/file: match must be one of \"exact\", \"starts-with\", \"ends-with\", \"contains\", \"regex\" [Got 'end']")) diff --git a/janet/test/doers/file.janet b/janet/test/doers/file.janet index d9c921b6..0873df6b 100644 --- a/janet/test/doers/file.janet +++ b/janet/test/doers/file.janet @@ -11,45 +11,44 @@ (import-tests "file" (curenv)) (test *collector* - @{:ensure @{:file @[{:_id "/test-role/file/_file_from_local_file" - :from "/gurpdir/files/file-test/does-not-exist" - :group "daemon" - :mode "0755" - :name "/file/from/local_file" - :owner "root" - :role "test-role"} - {:_id "/test-role/file/_file_from_content" - :content "lots-of-data" - :group "root" - :mode "0600" - :name "/file/from/content" - :owner "dataperson" - :role "test-role"} - {:_id "/test-role/file/remote-file" - :from-url "https://example.com/files/config" - :group "root" - :label "remote-file" - :mode "0640" - :name "/file/from/arbitrary/server" - :owner "gibbus" - :role "test-role" - :with-checksum "0123456789abcdef"}]} - :remove @{:file @[{:_id "/test-role/file/_path_to_file" - :name "/path/to/file" - :role "test-role"}]}})) + @{:ensure @{:file @[{:_id "/test-role/file/_file_from_local_file" + :from "/gurpdir/files/file-test/does-not-exist" + :group "daemon" + :mode "0755" + :name "/file/from/local_file" + :owner "root" + :role "test-role"} + {:_id "/test-role/file/_file_from_content" + :content "lots-of-data" + :group "root" + :mode "0600" + :name "/file/from/content" + :owner "dataperson" + :role "test-role"} + {:_id "/test-role/file/remote-file" + :from-url "https://example.com/files/config" + :group "root" + :label "remote-file" + :mode "0640" + :name "/file/from/arbitrary/server" + :owner "gibbus" + :role "test-role" + :with-checksum "0123456789abcdef"}]} + :remove @{:file @[{:_id "/test-role/file/_path_to_file" + :name "/path/to/file" + :role "test-role"}]}}) -(deftest file-error (test-error (file/ensure "/octals/only" :owner "merp" :group "byerp" :permissions "rwxr-xr-x") - "unexpected property :permissions. Valid properties are :owner, :content, :from-url, :group, :mode, :from-struct, :with-checksum, :from, :ignore-pattern, :to-format, :backup-suffix, :label")) + "In file/ensure /octals/only: unexpected property :permissions. Valid properties are :owner, :content, :from-url, :group, :mode, :from-struct, :with-checksum, :from, :ignore-pattern, :to-format, :backup-suffix, :label")) # In server mode local file references get turned into http ones, pointing # to the server. # -(deftest "file-resource-server" +(deftest file-resource-server (setdyn :gurp-config-root "/gurpdir") (setdyn :role-dyn "test-role") (setdyn :server-name "test-server") @@ -99,11 +98,3 @@ :remove @{:file @[{:_id "/test-role/file/_path_to_file" :name "/path/to/file" :role "test-role"}]}})) - -(deftest "file-error" - (test-error - (file/ensure "/octals/only" - :owner "merp" - :group "byerp" - :permissions "rwxr-xr-x") - "unexpected property :permissions. Valid properties are :owner, :content, :from-url, :group, :mode, :from-struct, :with-checksum, :from, :ignore-pattern, :to-format, :backup-suffix, :label")) diff --git a/janet/test/doers/gem.janet b/janet/test/doers/gem.janet index 964c607d..19fd5a68 100644 --- a/janet/test/doers/gem.janet +++ b/janet/test/doers/gem.janet @@ -21,10 +21,9 @@ :version "1.2.3"}]} :remove @{:gem @[{:_id "/test-role/gem/webscale" :name "webscale" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest gem-error (test-error (gem/ensure "wavefront-sdk" :merp 11) - "unexpected property :merp. Valid properties are :gem-path, :version, :source, :label")) + "In gem/ensure wavefront-sdk: unexpected property :merp. Valid properties are :gem-path, :version, :source, :label")) diff --git a/janet/test/doers/group.janet b/janet/test/doers/group.janet index 5022a577..137256bc 100644 --- a/janet/test/doers/group.janet +++ b/janet/test/doers/group.janet @@ -16,15 +16,14 @@ :role "test-role"}]} :remove @{:group @[{:_id "/test-role/group/old-group" :name "old-group" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest group-error (test-error (group/ensure "wat") - "did not find mandatory property :gid. Mandatory properties are :gid") + "In group/ensure wat: did not find mandatory property :gid. Mandatory properties are :gid") (test-error - (group/ensure "group" + (group/ensure "testusergroup" :gid 264 :gecos "Test User") - "unexpected property :gecos. Valid properties are :gid, :label")) + "In group/ensure testusergroup: unexpected property :gecos. Valid properties are :gid, :label")) diff --git a/janet/test/doers/ip-address.janet b/janet/test/doers/ip-address.janet index 00c24407..e44958b2 100644 --- a/janet/test/doers/ip-address.janet +++ b/janet/test/doers/ip-address.janet @@ -24,9 +24,8 @@ :type "dhcp"}]} :remove @{:ip-address @[{:_id "/test-role/ip-address/example2_v4" :name "example2/v4" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest ip-address-error (test-error (ip-address/ensure "bad0" :over "e1000g") - "did not find mandatory property :type. Mandatory properties are :type")) + "In ip-address/ensure bad0: did not find mandatory property :type. Mandatory properties are :type")) diff --git a/janet/test/doers/ip-interface.janet b/janet/test/doers/ip-interface.janet index 673bd598..d68c2081 100644 --- a/janet/test/doers/ip-interface.janet +++ b/janet/test/doers/ip-interface.janet @@ -10,15 +10,28 @@ (import-tests "ip-interface" (curenv)) (test *collector* - @{:ensure @{:ip-interface @[{:_id "/test-role/ip-interface/example0" - :name "example0" - :role "test-role"} - {:_id "/test-role/ip-interface/example-interface" - :label "example-interface" - :name "example1" - :protocols {:ipv4 {:forwarding true :mtu 1500} - :ipv6 {:forwarding false :mtu 1500}} - :role "test-role"}]} - :remove @{:ip-interface @[{:_id "/test-role/ip-interface/example2" - :name "example2" - :role "test-role"}]}})) + @{:ensure @{:ip-interface @[{:_id "/test-role/ip-interface/example0" + :name "example0" + :role "test-role"} + {:_id "/test-role/ip-interface/example-interface" + :label "example-interface" + :name "example1" + :protocols {:ipv4 {:forwarding true :mtu 1500} + :ipv6 {:forwarding false :mtu 1500}} + :role "test-role"}]} + :remove @{:ip-interface @[{:_id "/test-role/ip-interface/example2" + :name "example2" + :role "test-role"}]}}) + + (test-error + (ip-interface/ensure "bad0" :ipv5 {:forwarding true}) + "In ip-interface/ensure bad0: unexpected property :ipv5. Valid properties are :ipv6, :ipv4, :ip, :label, :udp, :icmp, :tcp, :sctp") + + (test-error + (ip-interface/ensure "bad1" :ipv4 "yay!") + "In ip-interface/ensure bad1: ipv4 is of type :string. Allowed types :struct, :table") + + (test-error + (ip-interface/remove "bad2" :ipv4 {:forwarding true}) + "In ip-interface/remove bad2: unexpected property :ipv4. Valid properties are :label")) + diff --git a/janet/test/doers/ip-properties.janet b/janet/test/doers/ip-properties.janet index fcfe4dd0..7f29dfe4 100644 --- a/janet/test/doers/ip-properties.janet +++ b/janet/test/doers/ip-properties.janet @@ -16,15 +16,14 @@ :ipv6 {:hoplimit 123 :hostmodel "weak"}} :name "general" :role "test-role"}]} - :remove @{}})) + :remove @{}}) -(deftest ip-properties-error (test-error (ip-properties/ensure "general" :ipv6 [1234567]) - "ipv6 is of type :tuple. Allowed types :struct, :table") + "In ip-properties/ensure general: ipv6 is of type :tuple. Allowed types :struct, :table") (test-error (ip-properties/ensure "general" :max-buf 1234567) - "unexpected property :max-buf. Valid properties are :ipv6, :ipv4, :ip, :label, :udp, :icmp, :tcp, :sctp")) + "In ip-properties/ensure general: unexpected property :max-buf. Valid properties are :ipv6, :ipv4, :ip, :label, :udp, :icmp, :tcp, :sctp")) diff --git a/janet/test/doers/ipfilter.janet b/janet/test/doers/ipfilter.janet index 1536b8c4..573294fe 100644 --- a/janet/test/doers/ipfilter.janet +++ b/janet/test/doers/ipfilter.janet @@ -24,13 +24,12 @@ :role "test-role"}]} :remove @{:ipfilter @[{:_id "/test-role/ipfilter/removes-all-rules" :name "removes-all-rules" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest ipfilter-errors (test-error (ipfilter/ensure "error-test-1" :from "test/ipfilter") - "did not find mandatory property :priority. Mandatory properties are :priority") + "In ipfilter/ensure error-test-1: did not find mandatory property :priority. Mandatory properties are :always-reload, :priority") (test-error (ipfilter/ensure "error-test-2" :priority 0) - "need exactly one of :content or :from")) + "In ipfilter/ensure error-test-2: need exactly one of :content or :from")) diff --git a/janet/test/doers/ipnat.janet b/janet/test/doers/ipnat.janet index 43684164..b99a4415 100644 --- a/janet/test/doers/ipnat.janet +++ b/janet/test/doers/ipnat.janet @@ -22,11 +22,12 @@ :role "test-role"}]} :remove @{:ipnat @[{:_id "/test-role/ipnat/removes-all-rules" :name "removes-all-rules" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest ipnat-errors (test-error (ipnat/ensure "error-test-1" :from "test/ipnat") - "did not find mandatory property :priority. Mandatory properties are :priority") + "In ipnat/ensure error-test-1: did not find mandatory property :priority. Mandatory properties are :priority") - (test-error (ipnat/ensure "error-test-2" :priority 0) "need exactly one of :content or :from")) + (test-error + (ipnat/ensure "error-test-2" :priority 0) + "In ipnat/ensure error-test-2: need exactly one of :content or :from")) diff --git a/janet/test/doers/link.janet b/janet/test/doers/link.janet new file mode 100644 index 00000000..5c7df4ac --- /dev/null +++ b/janet/test/doers/link.janet @@ -0,0 +1,37 @@ +(use judge) +(use ./test-lib) +(use ../../src/collector) +(use ../../src/dsl) +(import ../../src/doers/link) + +(deftest link + (set *collector* (new-collector)) + (setdyn :role-dyn "test-role") + + (import-tests "link" (curenv)) + + (test *collector* + @{:ensure @{:link @[{:_id "/test-role/link/example-symlink" + :label "example-symlink" + :name "/symlink/is/here" + :role "test-role" + :source "/link/points/here" + :type "symbolic"} + {:_id "/test-role/link/_link_is_here" + :name "/link/is/here" + :role "test-role" + :source "/link/points/here" + :type "hard"}]} + :remove @{:link @[{:_id "/test-role/link/_dont_want_this_link" + :name "/dont/want/this/link" + :role "test-role"}]}}) + + (test-error + (link/ensure "/where/does/this/point") + "In link/ensure /where/does/this/point: did not find mandatory property :source. Mandatory properties are :source, :type") + + (test-error + (link/ensure "/links/dont/work/like/that" + :source "/some/file" + :owner "me") + "In link/ensure /links/dont/work/like/that: unexpected property :owner. Valid properties are :source, :type, :label")) diff --git a/janet/test/doers/misc.janet b/janet/test/doers/misc.janet index b3cca725..9c829612 100644 --- a/janet/test/doers/misc.janet +++ b/janet/test/doers/misc.janet @@ -12,31 +12,30 @@ (misc/ensure :enable-smb "frances") (test *collector* - @{:ensure @{:misc @[{:_id "/test-role/misc/nfs-domain-lan.id264.net" - :name "nfs-domain-lan.id264.net" - :nfs-domain "lan.id264.net" - :role "test-role"} - {:_id "/test-role/misc/enable-smb-rob" - :enable-smb "rob" - :name "enable-smb-rob" - :role "test-role"} - {:_id "/test-role/misc/scheduler-FSS" - :name "scheduler-FSS" - :role "test-role" - :scheduler "FSS"} - {:_id "/test-role/misc/enable-smb-klf" - :enable-smb "klf" - :name "enable-smb-klf" - :role "test-role"} - {:_id "/test-role/misc/enable-smb-frances" - :enable-smb "frances" - :name "enable-smb-frances" - :role "test-role"}]} - :remove @{}})) + @{:ensure @{:misc @[{:_id "/test-role/misc/nfs-domain-lan.id264.net" + :name "nfs-domain-lan.id264.net" + :nfs-domain "lan.id264.net" + :role "test-role"} + {:_id "/test-role/misc/enable-smb-rob" + :enable-smb "rob" + :name "enable-smb-rob" + :role "test-role"} + {:_id "/test-role/misc/scheduler-FSS" + :name "scheduler-FSS" + :role "test-role" + :scheduler "FSS"} + {:_id "/test-role/misc/enable-smb-klf" + :enable-smb "klf" + :name "enable-smb-klf" + :role "test-role"} + {:_id "/test-role/misc/enable-smb-frances" + :enable-smb "frances" + :name "enable-smb-frances" + :role "test-role"}]} + :remove @{}}) -(deftest misc-error (test-error (misc/ensure :scheduler-class "FSS" :enable-smb "rob") - "unexpected property :scheduler-class. Valid properties are :scheduler, :nfs-domain, :enable-smb, :label")) + "In misc/ensure scheduler-class-FSS-enable-smb-rob: unexpected property :scheduler-class. Valid properties are :scheduler, :nfs-domain, :enable-smb, :label")) diff --git a/janet/test/doers/network-flow.janet b/janet/test/doers/network-flow.janet index d766fa85..a30151c9 100644 --- a/janet/test/doers/network-flow.janet +++ b/janet/test/doers/network-flow.janet @@ -60,9 +60,8 @@ :role "test-role"}]} :remove @{:network-flow @[{:_id "/test-role/network-flow/unwanted" :name "unwanted" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest network-flow-errors (test-error (network-flow/ensure "extraneous-property" :this-should-break-it true @@ -71,7 +70,7 @@ :local-port 80 :maxbw "10M" :priority "high") - "unexpected property :this-should-break-it. Valid properties are :link, :dsfield, :remote-port, :remote-ip, :priority, :protocol, :label, :local-port, :maxbw, :local-ip") + "In network-flow/ensure extraneous-property: unexpected property :this-should-break-it. Valid properties are :link, :dsfield, :remote-port, :remote-ip, :priority, :protocol, :label, :local-port, :maxbw, :local-ip") (test-error (network-flow/ensure "missing-link" @@ -79,4 +78,4 @@ :local-port 80 :maxbw "10M" :priority "high") - "did not find mandatory property :link. Mandatory properties are :link")) + "In network-flow/ensure missing-link: did not find mandatory property :link. Mandatory properties are :link")) diff --git a/janet/test/doers/pkg.janet b/janet/test/doers/pkg.janet index d187567a..2b1701d3 100644 --- a/janet/test/doers/pkg.janet +++ b/janet/test/doers/pkg.janet @@ -11,18 +11,17 @@ (pkg/remove "ooce/developer/python") (test *collector* - @{:ensure @{:pkg @[{:_id "/test-role/pkg/ooce_developer_rust" - :name "ooce/developer/rust" - :role "test-role"}]} - :remove @{:pkg @[{:_id "/test-role/pkg/ooce_developer_go" - :name "ooce/developer/go" - :role "test-role"} - {:_id "/test-role/pkg/ooce_developer_python" - :name "ooce/developer/python" - :role "test-role"}]}})) + @{:ensure @{:pkg @[{:_id "/test-role/pkg/ooce_developer_rust" + :name "ooce/developer/rust" + :role "test-role"}]} + :remove @{:pkg @[{:_id "/test-role/pkg/ooce_developer_go" + :name "ooce/developer/go" + :role "test-role"} + {:_id "/test-role/pkg/ooce_developer_python" + :name "ooce/developer/python" + :role "test-role"}]}}) -(deftest pkg-error (test-error (pkg/ensure "sysdef/tools/gurp" :version "1.1.1") - "unexpected property :version. Valid properties are :label")) + "In pkg/ensure sysdef/tools/gurp: unexpected property :version. Valid properties are :label")) diff --git a/janet/test/doers/pkgin.janet b/janet/test/doers/pkgin.janet index fc1ebd3e..9cccf0d5 100644 --- a/janet/test/doers/pkgin.janet +++ b/janet/test/doers/pkgin.janet @@ -19,11 +19,14 @@ :role "test-role"} {:_id "/test-role/pkgin/python" :name "python" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest pkgin-error + (test-error + (pkgin/remove "go" :version "1.20.1") + "In pkgin/remove go: unexpected property :version. Valid properties are :label") + (test-error (pkgin/ensure "gurp" :version "1.1.1") - "unexpected property :version. Valid properties are :label")) + "In pkgin/ensure gurp: unexpected property :version. Valid properties are :label")) diff --git a/janet/test/doers/publisher.janet b/janet/test/doers/publisher.janet index cf2254de..26a8325d 100644 --- a/janet/test/doers/publisher.janet +++ b/janet/test/doers/publisher.janet @@ -16,9 +16,12 @@ :uri "http://pkg.lan.id264.net"}]} :remove @{:publisher @[{:_id "/test-role/publisher/old_publisher" :name "old_publisher" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest publisher-error + (test-error + (publisher/remove "sysdef" :url "abc") + "In publisher/remove sysdef: unexpected property :url. Valid properties are :label") + (test-error (publisher/ensure "sysdef") - "did not find mandatory property :uri. Mandatory properties are :uri")) + "In publisher/ensure sysdef: did not find mandatory property :uri. Mandatory properties are :uri")) diff --git a/janet/test/doers/route.janet b/janet/test/doers/route.janet index 6e421f0c..da0f092d 100644 --- a/janet/test/doers/route.janet +++ b/janet/test/doers/route.janet @@ -13,44 +13,43 @@ (route/remove "192.168.1.1" :gateway "default") (test *collector* - @{:ensure @{:route @[{:_id "/test-role/route/default-gateway" - :force-gateway false - :gateway "default" - :label "default-gateway" - :name "192.168.1.1" - :role "test-role"} - {:_id "/test-role/route/10.0.5.0_24" - :flags {:mtu 1500} - :force-gateway false - :gateway "10.0.5.150" - :name "10.0.5.0/24" - :role "test-role"} - {:_id "/test-role/route/203.0.113.0_24" - :force-gateway false - :gateway "127.0.0.1" - :name "203.0.113.0/24" - :role "test-role" - :type "blackhole"} - {:_id "/test-role/route/192.168.1.0_24" - :force-gateway false - :interface "e1000g0" - :name "192.168.1.0/24" - :role "test-role"} - {:_id "/test-role/route/192.168.1.0_24" - :force-gateway true - :interface "router" - :name "192.168.1.0/24" - :role "test-role"}]} - :remove @{:route @[{:_id "/test-role/route/10.0.5.0_24" - :gateway "10.0.5.150" - :name "10.0.5.0/24" - :role "test-role"} - {:_id "/test-role/route/192.168.1.1" - :gateway "default" - :name "192.168.1.1" - :role "test-role"}]}})) + @{:ensure @{:route @[{:_id "/test-role/route/default-gateway" + :force-gateway false + :gateway "default" + :label "default-gateway" + :name "192.168.1.1" + :role "test-role"} + {:_id "/test-role/route/10.0.5.0_24" + :flags {:mtu 1500} + :force-gateway false + :gateway "10.0.5.150" + :name "10.0.5.0/24" + :role "test-role"} + {:_id "/test-role/route/203.0.113.0_24" + :force-gateway false + :gateway "127.0.0.1" + :name "203.0.113.0/24" + :role "test-role" + :type "blackhole"} + {:_id "/test-role/route/192.168.1.0_24" + :force-gateway false + :interface "e1000g0" + :name "192.168.1.0/24" + :role "test-role"} + {:_id "/test-role/route/192.168.1.0_24" + :force-gateway true + :interface "router" + :name "192.168.1.0/24" + :role "test-role"}]} + :remove @{:route @[{:_id "/test-role/route/10.0.5.0_24" + :gateway "10.0.5.150" + :name "10.0.5.0/24" + :role "test-role"} + {:_id "/test-role/route/192.168.1.1" + :gateway "default" + :name "192.168.1.1" + :role "test-role"}]}}) -(deftest route-error (test-error (route/ensure "192.168.1.1") "Provide exactly one of :gateway and :interface") @@ -65,4 +64,12 @@ (route/ensure "192.168.1.1" :gateway "default" :default "1.1.1.1") - "unexpected property :default. Valid properties are :type, :interface, :force-gateway, :label, :flags, :gateway")) + "In route/ensure 192.168.1.1: unexpected property :default. Valid properties are :type, :interface, :force-gateway, :label, :flags, :gateway") + + (test-error + (route/remove "192.168.1.1") + "In route/remove 192.168.1.1: did not find mandatory property :gateway. Mandatory properties are :gateway") + + (test-error + (route/remove "192.168.1.1" :gateway "default" :type "problem") + "In route/remove 192.168.1.1: unexpected property :type. Valid properties are :gateway, :label")) diff --git a/janet/test/doers/smf.janet b/janet/test/doers/smf.janet index b603b09a..de7756f3 100644 --- a/janet/test/doers/smf.janet +++ b/janet/test/doers/smf.janet @@ -9,48 +9,48 @@ (import-tests "smf" (curenv)) (test *collector* - @{:ensure @{:smf @[{:_id "/NO-ROLE/smf/example" - :default-enabled true - :dependencies @[@{:fmri "svc://example/service1:default" - :grouping "require_all" - :name "dependency1" - :restart-on "none" - :type "service"} - @{:fmri "svc://example/service2:default" - :grouping "optional-all" - :name "dependency2" - :restart-on "error" - :type "service"}] - :description "Run example program" - :fmri "snltd/example" - :name "example" - :properties @{:application/datadir {:type "astring" :value "/data"}} - :property-groups {:application "application"} - :role "NO-ROLE" - :single-instance true - :start-method @{:context {:group "daemon" - :privileges "basic,file_dac_search,sys_admin,proc_owner,proc_zone" - :user "example"} - :exec "/opt/site/lib/smf/method/example.sh" - :timeout 60} - :stop-method {:exec ":kill" :timeout 10}}]} - :remove @{:smf @[{:_id "/NO-ROLE/smf/unwanted_service" - :name "unwanted/service" - :role "NO-ROLE"}]}})) + @{:ensure @{:smf @[{:_id "/NO-ROLE/smf/example" + :default-enabled true + :dependencies @[@{:fmri "svc://example/service1:default" + :grouping "require_all" + :name "dependency1" + :restart-on "none" + :type "service"} + @{:fmri "svc://example/service2:default" + :grouping "optional-all" + :name "dependency2" + :restart-on "error" + :type "service"}] + :description "Run example program" + :fmri "snltd/example" + :name "example" + :properties @{:application/datadir {:type "astring" :value "/data"}} + :property-groups {:application "application"} + :role "NO-ROLE" + :single-instance true + :start-method @{:context {:group "daemon" + :privileges "basic,file_dac_search,sys_admin,proc_owner,proc_zone" + :user "example"} + :exec "/opt/site/lib/smf/method/example.sh" + :timeout 60} + :stop-method {:exec ":kill" :timeout 10}}]} + :remove @{:smf @[{:_id "/NO-ROLE/smf/unwanted_service" + :name "unwanted/service" + :role "NO-ROLE"}]}}) -(test-error - (smf/ensure "telegraf" - :description "Run Telegraf agent" - :fmri "sysdef/telegraf" - (smf/method "start" :exec "/opt/site/lib/smf/method/telegraf.sh") - (smf/method "refresh" :exec ":kill -THAW") - (smf/method "gibbus" :exec "gibbus")) - "smf/method name must be one of \"start\", \"stop\", \"refresh\", \"reload\"") + (test-error + (smf/ensure "telegraf" + :description "Run Telegraf agent" + :fmri "sysdef/telegraf" + (smf/method "start" :exec "/opt/site/lib/smf/method/telegraf.sh") + (smf/method "refresh" :exec ":kill -THAW") + (smf/method "gibbus" :exec "gibbus")) + "In smf/method gibbus: smf/method name must be one of \"start\", \"stop\", \"refresh\", \"reload\"") -(test-error - (smf/ensure "telegraf" - (smf/method "start" - :exec "/opt/site/lib/smf/method/telegraf.sh" - :user "telegraf" - :group "daemon")) - "did not find mandatory property :fmri. Mandatory properties are :fmri") + (test-error + (smf/ensure "telegraf" + (smf/method "start" + :exec "/opt/site/lib/smf/method/telegraf.sh" + :user "telegraf" + :group "daemon")) + "In smf/ensure telegraf: did not find mandatory property :fmri. Mandatory properties are :fmri")) diff --git a/janet/test/doers/smf/dependency.janet b/janet/test/doers/smf/dependency.janet index ebb6fa53..2b43c815 100644 --- a/janet/test/doers/smf/dependency.janet +++ b/janet/test/doers/smf/dependency.janet @@ -22,7 +22,8 @@ (test-error (smf/dependency "svc1" :service "svc://test/svc1:default") - "did not find mandatory property :fmri. Mandatory properties are :name, :fmri") + "In smf/dependency svc1: did not find mandatory property :fmri. Mandatory properties are :name, :fmri") + (test-error (smf/dependency "svc1" :fmri "svc://test/svc1:default" :junk "junk") - "unexpected property :junk. Valid properties are :name, :fmri, :grouping, :type, :restart-on, :label")) + "In smf/dependency svc1: unexpected property :junk. Valid properties are :name, :fmri, :grouping, :type, :restart-on, :label")) diff --git a/janet/test/doers/smf/dependent.janet b/janet/test/doers/smf/dependent.janet index 3a646322..734feff4 100644 --- a/janet/test/doers/smf/dependent.janet +++ b/janet/test/doers/smf/dependent.janet @@ -22,7 +22,7 @@ (test-error (smf/dependent "svc1" :service "svc://test/svc1:default") - "did not find mandatory property :fmri. Mandatory properties are :name, :fmri") + "In smf/dependent svc1: did not find mandatory property :fmri. Mandatory properties are :name, :fmri") (test-error (smf/dependent "svc1" :fmri "svc://test/svc1:default" :junk "junk") - "unexpected property :junk. Valid properties are :name, :fmri, :grouping, :type, :restart-on, :label")) + "In smf/dependent svc1: unexpected property :junk. Valid properties are :name, :fmri, :grouping, :type, :restart-on, :label")) diff --git a/janet/test/doers/smf/method.janet b/janet/test/doers/smf/method.janet index 79d82c70..7c54dd7c 100644 --- a/janet/test/doers/smf/method.janet +++ b/janet/test/doers/smf/method.janet @@ -13,5 +13,16 @@ :privileges "basic,file_dac_search,sys_admin,proc_owner,proc_zone" :user "telegraf"} :exec "/opt/site/lib/smf/method/telegraf.sh" - :timeout 60}})) + :timeout 60}}) + (test-error + (smf/method "explode" :exec "boom!") + "In smf/method explode: smf/method name must be one of \"start\", \"stop\", \"refresh\", \"reload\"") + + (test-error + (smf/method "start" :exec "/bin/prog" :role "boss") + "In smf/method start: unexpected property :role. Valid properties are :exec, :timeout, :user, :group, :environment, :label, :privileges") + + (test-error + (smf/method "start" :thing "whatever") + "In smf/method start: did not find mandatory property :exec. Mandatory properties are :exec, :timeout")) diff --git a/janet/test/doers/svc.janet b/janet/test/doers/svc.janet index 94d73f30..9d80bdd6 100644 --- a/janet/test/doers/svc.janet +++ b/janet/test/doers/svc.janet @@ -15,16 +15,15 @@ :restarted-by @["/test-role/file/stub"] :role "NO-ROLE" :state "enabled"}]} - :remove @{}})) + :remove @{}}) -(deftest svc-error (test-error (svc/ensure "too/many/keys" :state "enabled" :volume 11 :strings: 12) - "unexpected property :strings:. Valid properties are :state, :restarted-by, :reloaded-by, :label") + "In svc/ensure too/many/keys: unexpected property :strings:. Valid properties are :state, :restarted-by, :reloaded-by, :label") (test-error (svc/ensure "what/should/i/do") - "did not find mandatory property :state. Mandatory properties are :state")) + "In svc/ensure what/should/i/do: did not find mandatory property :state. Mandatory properties are :state")) diff --git a/janet/test/doers/svcprop.janet b/janet/test/doers/svcprop.janet index 00838328..16f19f91 100644 --- a/janet/test/doers/svcprop.janet +++ b/janet/test/doers/svcprop.janet @@ -9,25 +9,23 @@ (import-tests "svcprop" (curenv)) - (test *collector* - @{:ensure @{:svcprop @[{:_id "/test-role/svcprop/example_svc_1" - :name "example/svc_1" - :properties @{:application/active {:type "boolean" :value true} - :application/datadir {:type "astring" :value "/data"} - :application/timeout {:type "integer" :value 50}} - :role "test-role"} - {:_id "/test-role/svcprop/example_svc_1" - :name "example/svc_1" - :properties @{:application/datadir {:type "astring" :value "/data"}} - :property-groups {:application "application"} - :role "test-role"}]} - :remove @{:svcprop @[{:_id "/test-role/svcprop/example_svc_3" - :name "example/svc_3" - :properties ["application/thing"] - :role "test-role"}]}})) + @{:ensure @{:svcprop @[{:_id "/test-role/svcprop/example_svc_1" + :name "example/svc_1" + :properties @{:application/active {:type "boolean" :value true} + :application/datadir {:type "astring" :value "/data"} + :application/timeout {:type "integer" :value 50}} + :role "test-role"} + {:_id "/test-role/svcprop/example_svc_1" + :name "example/svc_1" + :properties @{:application/datadir {:type "astring" :value "/data"}} + :property-groups {:application "application"} + :role "test-role"}]} + :remove @{:svcprop @[{:_id "/test-role/svcprop/example_svc_3" + :name "example/svc_3" + :properties ["application/thing"] + :role "test-role"}]}}) -(deftest svcprop-error (test-error (svcprop/ensure "mariadb" :wat true) - "did not find mandatory property :properties. Mandatory properties are :properties")) + "In svcprop/ensure mariadb: did not find mandatory property :properties. Mandatory properties are :properties")) diff --git a/janet/test/doers/symlink.janet b/janet/test/doers/symlink.janet deleted file mode 100644 index 01bb48b8..00000000 --- a/janet/test/doers/symlink.janet +++ /dev/null @@ -1,32 +0,0 @@ -(use judge) -(use ./test-lib) -(use ../../src/collector) -(use ../../src/dsl) -(import ../../src/doers/symlink) - -(deftest symlink - (set *collector* (new-collector)) - (setdyn :role-dyn "test-role") - - (import-tests "symlink" (curenv)) - - (test *collector* - @{:ensure @{:symlink @[{:_id "/test-role/symlink/example-link" - :label "example-link" - :name "/link/is/here" - :role "test-role" - :source "/link/points/here"}]} - :remove @{:symlink @[{:_id "/test-role/symlink/_dont_want_this_link" - :name "/dont/want/this/link" - :role "test-role"}]}})) - -(deftest symlink-error - (test-error - (symlink/ensure "/where/does/this/point") - "did not find mandatory property :source. Mandatory properties are :source") - - (test-error - (symlink/ensure "/symlinks/dont/work/like/that" - :source "/some/file" - :owner "me") - "unexpected property :owner. Valid properties are :source, :label")) diff --git a/janet/test/doers/user.janet b/janet/test/doers/user.janet index f38a2be8..364e4090 100644 --- a/janet/test/doers/user.janet +++ b/janet/test/doers/user.janet @@ -21,13 +21,12 @@ :uid 264}]} :remove @{:user @[{:_id "/test-role/user/lolex" :name "lolex" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest user-error (test-error (user/ensure "wat" :uid 100) - "did not find mandatory property :home-dir. Mandatory properties are :home-dir, :primary-group, :uid, :gecos, :shell") + "In user/ensure wat: did not find mandatory property :home-dir. Mandatory properties are :home-dir, :primary-group, :uid, :gecos, :shell") (test-error (user/ensure "rob" @@ -39,4 +38,4 @@ :shell "/bin/zsh" :gecos "Test User" :password-hash "w0934cm-4i5c-42u5cn492hrc97h234ui") - "unexpected property :height. Valid properties are :home-dir, :primary-group, :uid, :gecos, :shell, :other-groups, :password-hash, :profiles, :label")) + "In user/ensure rob: unexpected property :height. Valid properties are :home-dir, :primary-group, :uid, :gecos, :shell, :other-groups, :password-hash, :profiles, :label")) diff --git a/janet/test/doers/vlan.janet b/janet/test/doers/vlan.janet index 6ec2b1d3..461f2419 100644 --- a/janet/test/doers/vlan.janet +++ b/janet/test/doers/vlan.janet @@ -17,16 +17,15 @@ :vlan-tag 10}]} :remove @{:vlan @[{:_id "/test-role/vlan/e1000g020" :name "e1000g020" - :role "test-role"}]}})) + :role "test-role"}]}}) -(deftest vlan-error (test-error (vlan/ensure "test-vlan-1") - "did not find mandatory property :over. Mandatory properties are :over, :vlan-tag") + "In vlan/ensure test-vlan-1: did not find mandatory property :over. Mandatory properties are :over, :vlan-tag") (test-error (vlan/ensure "test-vlan" :over "e1000g0" :vlan-tag 24 :with "field") - "unexpected property :with. Valid properties are :over, :vlan-tag, :label")) + "In vlan/ensure test-vlan: unexpected property :with. Valid properties are :over, :vlan-tag, :label")) diff --git a/janet/test/doers/vnic.janet b/janet/test/doers/vnic.janet index 08c67781..519af28f 100644 --- a/janet/test/doers/vnic.janet +++ b/janet/test/doers/vnic.janet @@ -10,26 +10,25 @@ (import-tests "vnic" (curenv)) (test *collector* - @{:ensure @{:vnic @[{:_id "/test-role/vnic/vnic0" - :name "vnic0" - :over "e1000g" - :role "test-role" - :with-interface false} - {:_id "/test-role/vnic/vnic1" - :name "vnic1" - :over "e1000g" - :role "test-role" - :vlan-tag 10 - :with-interface true}]} - :remove @{:vnic @[{:_id "/test-role/vnic/vnic2" - :name "vnic2" - :role "test-role"}]}})) + @{:ensure @{:vnic @[{:_id "/test-role/vnic/vnic0" + :name "vnic0" + :over "e1000g" + :role "test-role" + :with-interface false} + {:_id "/test-role/vnic/vnic1" + :name "vnic1" + :over "e1000g" + :role "test-role" + :vlan-tag 10 + :with-interface true}]} + :remove @{:vnic @[{:_id "/test-role/vnic/vnic2" + :name "vnic2" + :role "test-role"}]}}) -(deftest vnic-error (test-error (vnic/ensure "missing_link0") - "did not find mandatory property :over. Mandatory properties are :over") + "In vnic/ensure missing_link0: did not find mandatory property :over. Mandatory properties are :over") (test-error (vnic/ensure "bad_link0" :over "e1000g" :speed 100) - "unexpected property :speed. Valid properties are :over, :with-interface, :vlan-tag, :label")) + "In vnic/ensure bad_link0: unexpected property :speed. Valid properties are :over, :with-interface, :vlan-tag, :label")) diff --git a/janet/test/doers/zfs.janet b/janet/test/doers/zfs.janet index 57f97daa..f696dfd6 100644 --- a/janet/test/doers/zfs.janet +++ b/janet/test/doers/zfs.janet @@ -11,27 +11,26 @@ (import-tests "zfs" (curenv)) (test *collector* - @{:ensure @{:zfs @[{:_id "/test-role/zfs/zfs-example-1" - :label "zfs-example-1" - :name "tank/example/filesystem" - :properties {:compression "gzip9" - :dedup true - :devices false - :mountpoint "/example/mountpoint"} - :role "test-role"} - {:_id "/test-role/zfs/example-zfs-vol" - :label "example-zfs-vol" - :name "tank/example/volume" - :properties {:mountpoint "none"} - :role "test-role" - :size "10G"}]} - :remove @{:zfs @[{:_id "/test-role/zfs/tank_old_filesystem" - :name "tank/old/filesystem" - :role "test-role"}]}})) + @{:ensure @{:zfs @[{:_id "/test-role/zfs/zfs-example-1" + :label "zfs-example-1" + :name "tank/example/filesystem" + :properties {:compression "gzip9" + :dedup true + :devices false + :mountpoint "/example/mountpoint"} + :role "test-role"} + {:_id "/test-role/zfs/example-zfs-vol" + :label "example-zfs-vol" + :name "tank/example/volume" + :properties {:mountpoint "none"} + :role "test-role" + :size "10G"}]} + :remove @{:zfs @[{:_id "/test-role/zfs/tank_old_filesystem" + :name "tank/old/filesystem" + :role "test-role"}]}}) -(deftest zfs-error (test-error (zfs/ensure "pool/fs" :properties {:mountpoint "none"} :volume-size "100M") - "unexpected property :volume-size. Valid properties are :properties, :size, :label")) + "In zfs/ensure pool/fs: unexpected property :volume-size. Valid properties are :properties, :size, :label")) diff --git a/janet/test/doers/zone.janet b/janet/test/doers/zone.janet index f3a52727..aa5393ba 100644 --- a/janet/test/doers/zone.janet +++ b/janet/test/doers/zone.janet @@ -34,98 +34,108 @@ :exec-in ["/usr/bin/pkg refresh"]) (test *collector* - @{:ensure @{:zone @[{:_id "/test-role/zone/native-zone" - :autoboot true - :boot-after-install true - :bootstrap @{:hostname "native-zone" - :server "gurp.localnet"} - :brand "lipkg" - :clone-from "gold-zone" - :fs @[@{:dir "/home" - :special "/export/home" - :type "lofs"}] - :name "native-zone" - :net @[@{:allowed-address "192.168.1.101/24" - :defrouter "192.168.1.1" - :global-nic "auto" - :physical "test_net0"}] - :recreate 0 - :role "test-role" - :zonepath "/zones/native-zone"} - {:_id "/test-role/zone/bhyve-zone" - :autoboot false - :bhyve @{:boot-volume "tank/bhyve/test" - :cloudinit-struct {:network {:version 2}} - :image-path "/var/tmp/noble-server-cloudimg-amd64.img.raw" - :ram "4G" - :vcpus 4 - :wait-for-boot true} - :boot-after-install true - :brand "bhyve" - :dns {:domain "lan.id264.net" - :nameservers ["192.168.1.53" "192.168.1.1"]} - :name "bhyve-zone" - :net @[@{:allowed-address "192.168.1.102/24" - :global-nic "auto" - :physical "bhyve0"}] - :recreate 0 - :role "test-role" - :zonepath "/zones/bhyve-zone"} - {:_id "/test-role/zone/lx-zone" - :attr @[@{:name "kernel-ver" - :type "string" - :value "4.4"}] - :autoboot true - :boot-after-install true - :brand "lx" - :copy-in @{"/gurpdir/files/lx-test/f1" "/etc/file1" - "/gurpdir/files/lx-test/f2" "/bin/exec2"} - :exec-in ["/bin/exec1" "/bin/exec2"] - :final-state "reboot" - :lx-image "alpine" - :name "lx-zone" - :net @[@{:allowed-address "192.168.1.103/24" - :defrouter "192.168.1.1" - :global-nic "auto" - :physical "znet0"}] - :recreate 0 - :role "test-role" - :zonepath "/zones/lx-zone"} - {:_id "/test-role/zone/test-zone-bootstrap-file" - :autoboot true - :boot-after-install true - :bootstrap @{:file "/var/tmp/bootstrap.janet"} - :brand "lipkg" - :name "test-zone-bootstrap-file" - :net @[@{:allowed-address "192.168.1.33/24" - :defrouter "192.168.1.1" - :global-nic "auto" - :physical "test_net0"}] - :recreate 0 - :role "test-role" - :zonepath "/zones/test-zone-bootstrap-file"} - {:_id "/test-role/zone/test-zone-fat" - :autoboot false - :boot-after-install true - :brand "lipkg" - :datasets ["big/zone/fs"] - :dns {:domain "lan.id264.net" - :nameservers ["192.168.1.53" "192.168.1.1"]} - :exec-in ["/usr/bin/pkg refresh"] - :fs @[@{:dir "/home" - :special "/export/home" - :type "lofs"} - @{:dir "/data" - :special "/export/data" - :type "lofs"}] - :name "test-zone-fat" - :net @[@{:allowed-address "192.168.1.33/24" - :defrouter "192.168.1.1" - :global-nic "auto" - :physical "test_net0"}] - :recreate 0 - :role "test-role" - :zonepath "/zones/test-zone-fat"}]} - :remove @{:zone @[{:_id "/test-role/zone/unwanted-zone" - :name "unwanted-zone" - :role "test-role"}]}})) + @{:ensure @{:zone @[{:_id "/test-role/zone/native-zone" + :autoboot true + :boot-after-install true + :bootstrap @{:hostname "native-zone" + :server "gurp.localnet"} + :brand "lipkg" + :clone-from "gold-zone" + :fs @[@{:dir "/home" + :special "/export/home" + :type "lofs"}] + :name "native-zone" + :net @[@{:allowed-address "192.168.1.101/24" + :defrouter "192.168.1.1" + :global-nic "auto" + :physical "test_net0"}] + :recreate 0 + :role "test-role" + :zonepath "/zones/native-zone"} + {:_id "/test-role/zone/bhyve-zone" + :autoboot false + :bhyve @{:boot-volume "tank/bhyve/test" + :cloudinit-struct {:network {:version 2}} + :image-path "/var/tmp/noble-server-cloudimg-amd64.img.raw" + :ram "4G" + :vcpus 4 + :wait-for-boot true} + :boot-after-install true + :brand "bhyve" + :dns {:domain "lan.id264.net" + :nameservers ["192.168.1.53" "192.168.1.1"]} + :name "bhyve-zone" + :net @[@{:allowed-address "192.168.1.102/24" + :global-nic "auto" + :physical "bhyve0"}] + :recreate 0 + :role "test-role" + :zonepath "/zones/bhyve-zone"} + {:_id "/test-role/zone/lx-zone" + :attr @[@{:name "kernel-ver" + :type "string" + :value "4.4"}] + :autoboot true + :boot-after-install true + :brand "lx" + :copy-in @{"/gurpdir/files/lx-test/f1" "/etc/file1" + "/gurpdir/files/lx-test/f2" "/bin/exec2"} + :exec-in ["/bin/exec1" "/bin/exec2"] + :final-state "reboot" + :lx-image "alpine" + :name "lx-zone" + :net @[@{:allowed-address "192.168.1.103/24" + :defrouter "192.168.1.1" + :global-nic "auto" + :physical "znet0"}] + :recreate 0 + :role "test-role" + :zonepath "/zones/lx-zone"} + {:_id "/test-role/zone/test-zone-bootstrap-file" + :autoboot true + :boot-after-install true + :bootstrap @{:file "/var/tmp/bootstrap.janet"} + :brand "lipkg" + :name "test-zone-bootstrap-file" + :net @[@{:allowed-address "192.168.1.33/24" + :defrouter "192.168.1.1" + :global-nic "auto" + :physical "test_net0"}] + :recreate 0 + :role "test-role" + :zonepath "/zones/test-zone-bootstrap-file"} + {:_id "/test-role/zone/test-zone-fat" + :autoboot false + :boot-after-install true + :brand "lipkg" + :datasets ["big/zone/fs"] + :dns {:domain "lan.id264.net" + :nameservers ["192.168.1.53" "192.168.1.1"]} + :exec-in ["/usr/bin/pkg refresh"] + :fs @[@{:dir "/home" + :special "/export/home" + :type "lofs"} + @{:dir "/data" + :special "/export/data" + :type "lofs"}] + :name "test-zone-fat" + :net @[@{:allowed-address "192.168.1.33/24" + :defrouter "192.168.1.1" + :global-nic "auto" + :physical "test_net0"}] + :recreate 0 + :role "test-role" + :zonepath "/zones/test-zone-fat"}]} + :remove @{:zone @[{:_id "/test-role/zone/unwanted-zone" + :name "unwanted-zone" + :role "test-role"}]}}) + + (test-error + (zone/ensure "wat-brand") + "In zone/ensure wat-brand: did not find mandatory property :brand. Mandatory properties are :brand") + + (test-error + (zone/ensure "bad-key" + :brand "sparse" + :oops "wat") + "In zone/ensure bad-key: unexpected property :oops. Valid properties are :brand, :rctl, :copy-in, :lx-image, :ip-type, :limitpriv, :bootstrap, :final-state, :bhyve, :boot-after-install, :clone-from, :attr, :fs, :dns, :datasets, :net, :autoboot, :hostid, :bootstrap-from, :zonepath, :capped-memory, :label, :pool, :exec-in, :recreate")) diff --git a/janet/test/doers/zone/attr.janet b/janet/test/doers/zone/attr.janet index 0271a64f..ca0469ad 100644 --- a/janet/test/doers/zone/attr.janet +++ b/janet/test/doers/zone/attr.janet @@ -25,6 +25,15 @@ {:attr @{:name "kernel-ver" :type "string" :value "4.4"}}) + + (test-error + (zone/attr "huh?") + "In zone/attr huh?: did not find mandatory property :value. Mandatory properties are :name, :value") + + (test-error + (zone/attr "thing" :value 123 :oops "wat") + "In zone/attr thing: unexpected property :oops. Valid properties are :name, :value, :type, :label") + (test-error (zone/attr "thing" :type "astring") - "did not find mandatory property :value. Mandatory properties are :name, :value")) + "In zone/attr thing: did not find mandatory property :value. Mandatory properties are :name, :value")) diff --git a/janet/test/doers/zone/bhyve.janet b/janet/test/doers/zone/bhyve.janet index 4ecf6d48..e2dba364 100644 --- a/janet/test/doers/zone/bhyve.janet +++ b/janet/test/doers/zone/bhyve.janet @@ -17,7 +17,7 @@ (test-error (zone/bhyve) - "did not find mandatory property :ram. Mandatory properties are :ram, :boot-volume, :vcpus") + "In zone/bhyve NO-NAME: did not find mandatory property :ram. Mandatory properties are :ram, :boot-volume, :vcpus") (test-error (zone/bhyve :ram "3G" @@ -25,4 +25,4 @@ :image-path "/var/tmp/noble-server-cloudimg-amd64.img.raw" :boot-volume "tank/bhyve/test" :oops "wat?") - "unexpected property :oops. Valid properties are :ram, :boot-volume, :vcpus, :cloudinit-files, :image-path, :label, :image-url, :image-format, :wait-for-boot, :cloudinit-struct")) + "In zone/bhyve NO-NAME: unexpected property :oops. Valid properties are :ram, :boot-volume, :vcpus, :cloudinit-files, :image-path, :label, :image-url, :image-format, :wait-for-boot, :cloudinit-struct")) diff --git a/janet/test/doers/zone/bootstrap.janet b/janet/test/doers/zone/bootstrap.janet index 5acde1b1..7240cd24 100644 --- a/janet/test/doers/zone/bootstrap.janet +++ b/janet/test/doers/zone/bootstrap.janet @@ -14,4 +14,4 @@ (test-error (zone/bootstrap :oops "wat?") - "unexpected property :oops. Valid properties are :label, :file, :server, :hostname")) + "In zone/bootstrap NO-NAME: unexpected property :oops. Valid properties are :label, :file, :server, :hostname")) diff --git a/janet/test/doers/zone/fs.janet b/janet/test/doers/zone/fs.janet index 462f3f60..f54fbb9e 100644 --- a/janet/test/doers/zone/fs.janet +++ b/janet/test/doers/zone/fs.janet @@ -14,9 +14,11 @@ :type "lofs"}}) (test-error - (zone/fs "/home" :special "/export/home" :oops "wat?") - "unexpected property :oops. Valid properties are :dir, :special, :type, :options, :label") + (zone/fs "/home" + :special "/export/home" + :oops "wat?") + "In zone/fs /home: unexpected property :oops. Valid properties are :dir, :special, :type, :options, :label") (test-error (zone/fs "/dir") - "did not find mandatory property :special. Mandatory properties are :dir, :special")) + "In zone/fs /dir: did not find mandatory property :special. Mandatory properties are :dir, :special")) diff --git a/janet/test/doers/zone/network.janet b/janet/test/doers/zone/network.janet index d456db91..aaa00b01 100644 --- a/janet/test/doers/zone/network.janet +++ b/janet/test/doers/zone/network.janet @@ -7,14 +7,19 @@ {:net @{:global-nic "auto" :physical "test_net0"}}) (test - (zone/network "test_net0" + (zone/network "test_net1" :allowed-address "1.2.3.4" :defrouter "1.2.3.1") {:net @{:allowed-address "1.2.3.4" :defrouter "1.2.3.1" :global-nic "auto" - :physical "test_net0"}}) + :physical "test_net1"}}) + + (test-error + (zone/network "test_net_2" + :global-nic 0) + "In zone/network NO-NAME: global-nic is of type :number. Allowed types :string") (test-error - (zone/network "test_net0" :oops "wat?") - "unexpected property :oops. Valid properties are :physical, :global-nic, :defrouter, :allowed-address, :label")) + (zone/network "test_net3" :oops "wat?") + "In zone/network NO-NAME: unexpected property :oops. Valid properties are :physical, :global-nic, :defrouter, :allowed-address, :label")) diff --git a/janet/test/doers/zone/rctl.janet b/janet/test/doers/zone/rctl.janet index 9f520edc..1faf351d 100644 --- a/janet/test/doers/zone/rctl.janet +++ b/janet/test/doers/zone/rctl.janet @@ -19,6 +19,12 @@ :name "zone.max-physical-memory" :priv "privileged"}}) + (test-error + (zone/rctl "zone.max-physical-memory" + :limit 524288000 + :oops "wat?") + "In zone/rctl zone.max-physical-memory: unexpected property :oops. Valid properties are :priv, :name, :action, :limit, :label") + (test-error (zone/rctl "zone.max-physical-memory") - "did not find mandatory property :limit. Mandatory properties are :priv, :name, :action, :limit")) + "In zone/rctl zone.max-physical-memory: did not find mandatory property :limit. Mandatory properties are :priv, :name, :action, :limit"))