greentic-start-dev 1.1.26572933845

Greentic lifecycle runner for start/restart/stop orchestration
Documentation
use std::io::{self, BufRead, IsTerminal, Write};

use crate::operator_i18n::tr;
use crate::{CloudflaredModeArg, NgrokModeArg, StartRequest};

pub(crate) fn maybe_prompt_tunnel(request: &mut StartRequest) {
    if request.tunnel_explicit {
        return;
    }
    if cfg!(test) {
        return;
    }
    if !io::stdin().is_terminal() || !io::stdout().is_terminal() {
        return;
    }

    let stdin = io::stdin();
    let stdout = io::stdout();
    let mut reader = stdin.lock();
    let mut writer = stdout.lock();
    prompt_tunnel_with_io(&mut reader, &mut writer, request);
}

fn prompt_tunnel_with_io<R: BufRead, W: Write>(
    reader: &mut R,
    writer: &mut W,
    request: &mut StartRequest,
) {
    let prompt = tr(
        "cli.tunnel.prompt",
        "Tunnel service (for external provider webhooks):",
    );
    let opt_none = tr("cli.tunnel.option_none", "No tunnel (local only)");
    let opt_cloudflared = tr(
        "cli.tunnel.option_cloudflared",
        "Cloudflare Tunnel (cloudflared)",
    );
    let opt_ngrok = tr("cli.tunnel.option_ngrok", "ngrok");

    let _ = writeln!(writer, "\n{prompt}");
    let _ = writeln!(writer, "1 ) {opt_none}");
    let _ = writeln!(writer, "2 ) {opt_cloudflared}");
    let _ = writeln!(writer, "3 ) {opt_ngrok}");
    let _ = write!(writer, "> ");
    let _ = writer.flush();

    let mut input = String::new();
    if reader.read_line(&mut input).is_err() {
        return;
    }

    match input.trim() {
        "2" => {
            request.cloudflared = CloudflaredModeArg::On;
        }
        "3" => {
            request.ngrok = NgrokModeArg::On;
        }
        "1" | "" => {
            let msg = tr("cli.tunnel.selected_none", "Running without tunnel.");
            let _ = writeln!(writer, "{msg}");
        }
        _ => {
            let msg = tr(
                "cli.tunnel.invalid_selection",
                "Invalid selection. Running without tunnel.",
            );
            let _ = writeln!(writer, "{msg}");
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::io::Cursor;

    fn make_request() -> StartRequest {
        StartRequest {
            bundle: None,
            tenant: None,
            team: None,
            no_nats: false,
            nats: crate::NatsModeArg::Off,
            nats_url: None,
            config: None,
            cloudflared: CloudflaredModeArg::Off,
            cloudflared_binary: None,
            ngrok: NgrokModeArg::Off,
            ngrok_binary: None,
            runner_binary: None,
            restart: Vec::new(),
            log_dir: None,
            verbose: false,
            quiet: false,
            no_browser: false,
            admin: false,
            admin_port: 8443,
            admin_certs_dir: None,
            admin_allowed_clients: Vec::new(),
            tunnel_explicit: false,
        }
    }

    #[test]
    fn selects_no_tunnel_on_option_1() {
        let mut input = Cursor::new(b"1\n".to_vec());
        let mut output = Vec::new();
        let mut request = make_request();
        prompt_tunnel_with_io(&mut input, &mut output, &mut request);
        assert_eq!(request.cloudflared, CloudflaredModeArg::Off);
        assert_eq!(request.ngrok, NgrokModeArg::Off);
    }

    #[test]
    fn selects_cloudflared_on_option_2() {
        let mut input = Cursor::new(b"2\n".to_vec());
        let mut output = Vec::new();
        let mut request = make_request();
        prompt_tunnel_with_io(&mut input, &mut output, &mut request);
        assert_eq!(request.cloudflared, CloudflaredModeArg::On);
        assert_eq!(request.ngrok, NgrokModeArg::Off);
    }

    #[test]
    fn selects_ngrok_on_option_3() {
        let mut input = Cursor::new(b"3\n".to_vec());
        let mut output = Vec::new();
        let mut request = make_request();
        prompt_tunnel_with_io(&mut input, &mut output, &mut request);
        assert_eq!(request.cloudflared, CloudflaredModeArg::Off);
        assert_eq!(request.ngrok, NgrokModeArg::On);
    }

    #[test]
    fn defaults_to_no_tunnel_on_empty_input() {
        let mut input = Cursor::new(b"\n".to_vec());
        let mut output = Vec::new();
        let mut request = make_request();
        prompt_tunnel_with_io(&mut input, &mut output, &mut request);
        assert_eq!(request.cloudflared, CloudflaredModeArg::Off);
        assert_eq!(request.ngrok, NgrokModeArg::Off);
    }

    #[test]
    fn defaults_to_no_tunnel_on_invalid_input() {
        let mut input = Cursor::new(b"abc\n".to_vec());
        let mut output = Vec::new();
        let mut request = make_request();
        prompt_tunnel_with_io(&mut input, &mut output, &mut request);
        assert_eq!(request.cloudflared, CloudflaredModeArg::Off);
        assert_eq!(request.ngrok, NgrokModeArg::Off);
    }

    #[test]
    fn renders_prompt_with_options() {
        let mut input = Cursor::new(b"1\n".to_vec());
        let mut output = Vec::new();
        let mut request = make_request();
        prompt_tunnel_with_io(&mut input, &mut output, &mut request);
        let rendered = String::from_utf8(output).expect("utf8");
        assert!(rendered.contains("1 )"));
        assert!(rendered.contains("2 )"));
        assert!(rendered.contains("3 )"));
    }
}