max / makenotwork
1 file changed,
+111 insertions,
-0 deletions
| @@ -23,6 +23,16 @@ | |||
| 23 | 23 | ||
| 24 | 24 | #[tokio::main] | |
| 25 | 25 | async fn main() { | |
| 26 | + | // Inspection flags first, before anything that reads the environment. | |
| 27 | + | // `--version` is how a deploy is verified: the controller's matrix says | |
| 28 | + | // which artifact was placed, only the running binary can say which one is | |
| 29 | + | // executing. Loading `Config` first made that check impossible on a box | |
| 30 | + | // without the unit's `EnvironmentFile` sourced, since a missing | |
| 31 | + | // `DATABASE_URL` panicked before the flag was ever looked at. | |
| 32 | + | if let Some(code) = handle_inspection_flags() { | |
| 33 | + | std::process::exit(code); | |
| 34 | + | } | |
| 35 | + | ||
| 26 | 36 | dotenvy::dotenv().ok(); | |
| 27 | 37 | ||
| 28 | 38 | // Before any TLS client is built; see the function's own docs for why this | |
| @@ -803,6 +813,107 @@ | |||
| 803 | 813 | "hard-exit must outlast the bounded request + pool drains, with margin" | |
| 804 | 814 | ); | |
| 805 | 815 | ||
| 816 | + | /// What an inspection flag asks for. Both answers are printable without a | |
| 817 | + | /// database, an env file, or anything else the deployed environment supplies. | |
| 818 | + | #[derive(Debug, PartialEq, Eq)] | |
| 819 | + | enum Inspection { | |
| 820 | + | Version, | |
| 821 | + | Help, | |
| 822 | + | } | |
| 823 | + | ||
| 824 | + | /// Classify the arguments, without printing. Unknown arguments are left alone: | |
| 825 | + | /// the binary takes other flags (`--seed-examples`) later in startup, and this | |
| 826 | + | /// pass is not an argument parser. | |
| 827 | + | fn inspection_request<I, S>(args: I) -> Option<Inspection> | |
| 828 | + | where | |
| 829 | + | I: IntoIterator<Item = S>, | |
| 830 | + | S: AsRef<str>, | |
| 831 | + | { | |
| 832 | + | args.into_iter().find_map(|arg| match arg.as_ref() { | |
| 833 | + | "--version" | "-V" => Some(Inspection::Version), | |
| 834 | + | "--help" | "-h" => Some(Inspection::Help), | |
| 835 | + | _ => None, | |
| 836 | + | }) | |
| 837 | + | } | |
| 838 | + | ||
| 839 | + | /// `name version (git sha)`, the same string `/health` reports, so the two can | |
| 840 | + | /// be compared directly when verifying a release. | |
| 841 | + | fn version_line() -> String { | |
| 842 | + | match option_env!("GIT_HASH") { | |
| 843 | + | Some(hash) if !hash.is_empty() => { | |
| 844 | + | format!("makenotwork {} ({hash})", env!("CARGO_PKG_VERSION")) | |
| 845 | + | } | |
| 846 | + | _ => format!("makenotwork {}", env!("CARGO_PKG_VERSION")), | |
| 847 | + | } | |
| 848 | + | } | |
| 849 | + | ||
| 850 | + | const HELP: &str = "\ | |
| 851 | + | makenotwork, the MNW server. | |
| 852 | + | ||
| 853 | + | Usage: makenotwork [FLAGS] | |
| 854 | + | ||
| 855 | + | Flags: | |
| 856 | + | -V, --version Print the version and exit | |
| 857 | + | -h, --help Print this help and exit | |
| 858 | + | --seed-examples Seed the staging example marketplace, then exit. Refuses | |
| 859 | + | against production; see makenotwork::seed. | |
| 860 | + | ||
| 861 | + | The server takes its configuration from the environment (see .env.example). | |
| 862 | + | Deployment tooling drives these modes by environment variable rather than by | |
| 863 | + | flag, because each one runs against a target with its env already sourced: | |
| 864 | + | ||
| 865 | + | MNW_CHECK_CONFIG=1 Load the config, print a sentinel line, exit. No database. | |
| 866 | + | MNW_CHECK_DOCS=1 Report broken internal doc links, exit. No database. | |
| 867 | + | SANDO_BOOT_SMOKE=1 Serve a minimal /health on SANDO_BOOT_SMOKE_PORT and idle. | |
| 868 | + | ||
| 869 | + | Running with no flags starts the server: config, pool, migrations, then serve."; | |
| 870 | + | ||
| 871 | + | /// Answer `--version` and `--help` before the process touches its environment. | |
| 872 | + | /// Returns the exit code when a flag was handled, `None` to carry on booting. | |
| 873 | + | fn handle_inspection_flags() -> Option<i32> { | |
| 874 | + | match inspection_request(std::env::args().skip(1))? { | |
| 875 | + | Inspection::Version => println!("{}", version_line()), | |
| 876 | + | Inspection::Help => println!("{HELP}"), | |
| 877 | + | } | |
| 878 | + | Some(0) | |
| 879 | + | } | |
| 880 | + | ||
| 881 | + | #[cfg(test)] | |
| 882 | + | mod inspection_tests { | |
| 883 | + | use super::*; | |
| 884 | + | ||
| 885 | + | #[test] | |
| 886 | + | fn version_flags_are_recognised() { | |
| 887 | + | assert_eq!(inspection_request(["--version"]), Some(Inspection::Version)); | |
| 888 | + | assert_eq!(inspection_request(["-V"]), Some(Inspection::Version)); | |
| 889 | + | } | |
| 890 | + | ||
| 891 | + | #[test] | |
| 892 | + | fn help_flags_are_recognised() { | |
| 893 | + | assert_eq!(inspection_request(["--help"]), Some(Inspection::Help)); | |
| 894 | + | assert_eq!(inspection_request(["-h"]), Some(Inspection::Help)); | |
| 895 | + | } | |
| 896 | + | ||
| 897 | + | #[test] | |
| 898 | + | fn boot_arguments_are_not_inspection_requests() { | |
| 899 | + | assert_eq!(inspection_request(Vec::<String>::new()), None); | |
| 900 | + | assert_eq!(inspection_request(["--seed-examples"]), None); | |
| 901 | + | } | |
| 902 | + | ||
| 903 | + | #[test] | |
| 904 | + | fn the_first_inspection_flag_wins() { | |
| 905 | + | assert_eq!( | |
| 906 | + | inspection_request(["--seed-examples", "--version", "--help"]), | |
| 907 | + | Some(Inspection::Version) | |
| 908 | + | ); | |
| 909 | + | } | |
| 910 | + | ||
| 911 | + | #[test] | |
| 912 | + | fn version_line_carries_the_crate_version() { | |
| 913 | + | assert!(version_line().starts_with(&format!("makenotwork {}", env!("CARGO_PKG_VERSION")))); | |
| 914 | + | } | |
| 915 | + | } | |
| 916 | + | ||
| 806 | 917 | /// Wait for SIGINT (Ctrl-C) or SIGTERM, then return to trigger graceful shutdown. | |
| 807 | 918 | /// In-flight requests get up to `SHUTDOWN_REQUEST_DRAIN_SECS` to complete, then | |
| 808 | 919 | /// the post-serve drains get `SHUTDOWN_POOL_DRAIN_SECS`; the hard-exit timer |