use clap::Args;
use colorful::Colorful;
use miette::{miette, IntoDiagnostic, WrapErr};
use serde_json::json;
use tokio::{sync::Mutex, try_join};
use ockam::identity::DEFAULT_TIMEOUT;
use ockam::{identity::Identifier, route, Context};
use ockam_api::address::extract_address_value;
use ockam_api::nodes::models::secure_channel::{
CreateSecureChannelRequest, CreateSecureChannelResponse,
};
use ockam_api::nodes::BackgroundNode;
use ockam_api::route_to_multiaddr;
use ockam_core::api::Request;
use ockam_multiaddr::MultiAddr;
use crate::docs;
use crate::identity::{get_identity_name, initialize_identity_if_default};
use crate::project::util::{
clean_projects_multiaddr, get_projects_secure_channels_from_config_lookup,
};
use crate::util::api::CloudOpts;
use crate::util::clean_nodes_multiaddr;
use crate::{
error::Error,
fmt_log, fmt_ok,
terminal::OckamColor,
util::{exitcode, node_rpc},
CommandGlobalOpts,
};
const LONG_ABOUT: &str = include_str!("./static/create/long_about.txt");
const AFTER_LONG_HELP: &str = include_str!("./static/create/after_long_help.txt");
#[derive(Clone, Debug, Args)]
#[command(
arg_required_else_help = true,
long_about = docs::about(LONG_ABOUT),
after_long_help = docs::after_help(AFTER_LONG_HELP),
)]
pub struct CreateCommand {
#[arg(value_name = "NODE", long, display_order = 800)]
pub from: String,
#[arg(value_name = "ROUTE", long, display_order = 800)]
pub to: MultiAddr,
#[arg(value_name = "IDENTIFIER", long, short, display_order = 801)]
pub authorized: Option<Vec<Identifier>>,
#[command(flatten)]
cloud_opts: CloudOpts,
#[arg(short, long)]
pub credential: Option<String>,
}
impl CreateCommand {
pub fn run(self, opts: CommandGlobalOpts) {
initialize_identity_if_default(&opts, &self.cloud_opts.identity);
node_rpc(rpc, (opts, self));
}
async fn parse_to_route(
&self,
opts: &CommandGlobalOpts,
ctx: &Context,
node: &BackgroundNode,
) -> miette::Result<MultiAddr> {
let (to, meta) = clean_nodes_multiaddr(&self.to, &opts.state)
.into_diagnostic()
.wrap_err(format!("Could not convert {} into route", &self.to))?;
let identity_name = get_identity_name(&opts.state, &self.cloud_opts.identity);
let projects_sc = get_projects_secure_channels_from_config_lookup(
opts,
ctx,
node,
&meta,
Some(identity_name),
Some(DEFAULT_TIMEOUT),
)
.await?;
clean_projects_multiaddr(to, projects_sc)
.into_diagnostic()
.wrap_err("Could not parse projects from route")
}
fn parse_from_node(&self) -> miette::Result<String> {
extract_address_value(&self.from).into_diagnostic()
}
}
async fn rpc(ctx: Context, (opts, cmd): (CommandGlobalOpts, CreateCommand)) -> miette::Result<()> {
opts.terminal
.write_line(&fmt_log!("Creating Secure Channel...\n"))?;
let is_finished: Mutex<bool> = Mutex::new(false);
let from = cmd.parse_from_node()?;
let node = BackgroundNode::create(&ctx, &opts.state, &from).await?;
let to = cmd.parse_to_route(&opts, &ctx, &node).await?;
let authorized_identifiers = cmd.authorized.clone();
let create_secure_channel = async {
let identity_name = get_identity_name(&opts.state, &cmd.cloud_opts.identity);
let payload = CreateSecureChannelRequest::new(
&to,
authorized_identifiers,
Some(identity_name),
cmd.credential.clone(),
);
let request = Request::post("/node/secure_channel").body(payload);
let response: CreateSecureChannelResponse = node.ask(&ctx, request).await?;
*is_finished.lock().await = true;
Ok(response.addr)
};
let output_messages = vec!["Creating Secure Channel...".to_string()];
let progress_output = opts
.terminal
.progress_output(&output_messages, &is_finished);
let (secure_channel, _) = try_join!(create_secure_channel, progress_output)?;
let route = &route![secure_channel.to_string()];
let multi_addr = route_to_multiaddr(route).ok_or_else(|| {
Error::new(
exitcode::PROTOCOL,
miette!("Failed to convert route {route} to multi-address"),
)
})?;
let from = format!("/node/{}", from);
opts.terminal
.stdout()
.plain(
fmt_ok!(
"Secure Channel at {} created successfully\n",
multi_addr
.to_string()
.color(OckamColor::PrimaryResource.color())
) + &fmt_log!(
"From {} to {}",
from.color(OckamColor::PrimaryResource.color()),
cmd.to
.to_string()
.color(OckamColor::PrimaryResource.color())
),
)
.machine(multi_addr.to_string())
.json(json!([{ "address": multi_addr.to_string() }]))
.write_line()?;
Ok(())
}