use crate::run::Config;
use crate::util::parsers::hostname_parser;
use crate::{docs, CommandGlobalOpts};
use clap::Args;
use colorful::Colorful;
use indoc::formatdoc;
use ockam::transport::SchemeHostnamePort;
use ockam_api::fmt_info;
use ockam_node::Context;
const LONG_ABOUT: &str = include_str!("./static/secure_relay_outlet/long_about.txt");
const AFTER_LONG_HELP: &str = include_str!("./static/secure_relay_outlet/after_long_help.txt");
#[derive(Clone, Debug, Args)]
#[command(
long_about = docs::about(LONG_ABOUT),
after_long_help = docs::after_help(AFTER_LONG_HELP)
)]
pub struct SecureRelayOutlet {
#[arg(value_name = "SERVICE NAME")]
pub service_name: String,
#[arg(long, display_order = 902, id = "SOCKET_ADDRESS", value_parser = hostname_parser)]
to: SchemeHostnamePort,
#[arg(long)]
dry_run: bool,
#[command(flatten)]
enroll: Enroll,
}
#[derive(Clone, Debug, Args)]
#[group(required = true, multiple = false)]
struct Enroll {
#[arg(
long,
value_name = "ENROLLMENT TICKET PATH",
group = "authentication_method"
)]
pub enroll_ticket: Option<String>,
#[arg(long = "okta", group = "authentication_method")]
pub okta: bool,
}
impl SecureRelayOutlet {
pub fn name(&self) -> String {
"show relay outlet".into()
}
pub async fn run(&self, ctx: &Context, opts: CommandGlobalOpts) -> miette::Result<()> {
self.create_config_and_start(ctx, opts).await
}
pub async fn create_config_and_start(
&self,
ctx: &Context,
opts: CommandGlobalOpts,
) -> miette::Result<()> {
let recipe: String = self.create_config_recipe();
if self.dry_run {
opts.terminal.write_line(recipe.as_str())?;
return Ok(());
}
opts.terminal.write_line(fmt_info!(
r#"Creating new outlet relay node using this configuration:
```
{}```
You can copy and customize the above recipe and launch it with `ockam run`.
"#,
recipe.as_str().dark_gray()
))?;
Config::parse_and_run(ctx, opts, recipe).await
}
fn create_config_recipe(&self) -> String {
let projects = if let Some(t) = self.enroll.enroll_ticket.as_ref() {
formatdoc! {r#"
projects:
enroll:
- ticket: {t}
"#}
} else {
"".to_string()
};
let recipe: String = formatdoc! {r#"
{projects}
policies:
- resource: 'tcp-outlet'
expression: '(= subject.component "{service_name}")'
tcp-outlets:
{service_name}:
to: {to}
relays:
- {service_name}
"#,
to = self.to,
service_name = self.service_name,
};
recipe
}
}
#[cfg(test)]
mod tests {
use super::*;
use ockam::transport::SchemeHostnamePort;
use ockam_api::cli_state::ExportedEnrollmentTicket;
#[test]
fn test_that_recipe_is_valid() {
let enrollment_ticket = ExportedEnrollmentTicket::new_test();
let enrollment_ticket_encoded = enrollment_ticket.to_string();
let cmd = SecureRelayOutlet {
service_name: "service_name".to_string(),
to: SchemeHostnamePort::new("tcp", "127.0.0.1", 8080).unwrap(),
dry_run: false,
enroll: Enroll {
enroll_ticket: Some(enrollment_ticket_encoded),
okta: false,
},
};
let config_recipe = cmd.create_config_recipe();
let config = Config::parse(config_recipe).unwrap();
config.project_enroll.into_parsed_commands(None).unwrap();
config.policies.into_parsed_commands().unwrap();
config.tcp_outlets.into_parsed_commands(None).unwrap();
config.relays.into_parsed_commands(None).unwrap();
}
}