From 9944b7dc214741106fb9a1a19a474ef97f68ee87 Mon Sep 17 00:00:00 2001 From: Davide Baldo Date: Fri, 14 Feb 2025 12:12:23 +0100 Subject: [PATCH] fix(rust): node control api fixes and error handling refactorings --- .../control_api/backend/authority_member.rs | 117 ++++++------------ .../src/control_api/backend/common.rs | 96 ++++++++------ .../src/control_api/backend/entrypoint.rs | 79 ++++++++---- .../src/control_api/backend/inlet.rs | 97 ++++++++++----- .../src/control_api/backend/outlet.rs | 71 ++++++----- .../src/control_api/backend/relay.rs | 58 +++++---- .../src/control_api/backend/ticket.rs | 86 +++++++------ .../ockam_api/src/control_api/frontend.rs | 11 +- .../ockam/ockam_api/src/control_api/http.rs | 64 ++++++---- .../ockam/ockam_api/src/control_api/mod.rs | 37 ++++++ .../src/node/create/foreground.rs | 4 +- .../ockam/ockam_command/src/service/config.rs | 21 ++-- .../tests/bats/orchestrator/control_api.bats | 2 +- 13 files changed, 432 insertions(+), 311 deletions(-) diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/authority_member.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/authority_member.rs index d3a40e35604..ef9fbfe3568 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/authority_member.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/authority_member.rs @@ -7,11 +7,10 @@ use crate::control_api::protocol::authority_member::{ AddOrUpdateAuthorityMemberRequest, AuthorityMember, GetAuthorityMemberRequest, ListAuthorityMembersRequest, RemoveAuthorityMemberRequest, }; +use crate::control_api::ControlApiError; use crate::nodes::NodeManager; use http::StatusCode; -use ockam::identity::Identifier; use ockam_node::Context; -use std::str::FromStr; use std::sync::Arc; impl HttpControlNodeApiBackend { @@ -21,10 +20,15 @@ impl HttpControlNodeApiBackend { method: &str, resource_id: Option<&str>, body: Option>, - ) -> ockam_core::Result { + ) -> Result { + let resource_name = "authority-member"; + let resource_name_identifier = "authority_member_identity"; match method { "PUT" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => { handle_authority_member_add_or_update(context, &self.node_manager, body, id) .await @@ -37,14 +41,17 @@ impl HttpControlNodeApiBackend { } }, "DELETE" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => { handle_authority_member_remove(context, &self.node_manager, body, id).await } }, _ => { warn!("Invalid method: {method}"); - ControlApiHttpResponse::invalid_method() + ControlApiHttpResponse::invalid_method(method, vec!["PUT", "GET", "DELETE"]) } } } @@ -74,32 +81,19 @@ async fn handle_authority_member_add_or_update( node_manager: &Arc, body: Option>, member_identity: &str, -) -> ockam_core::Result { - let request: AddOrUpdateAuthorityMemberRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: AddOrUpdateAuthorityMemberRequest = common::parse_request_body(body)?; + let member_identity = + common::parse_identifier(member_identity, "Invalid authority member identity")?; let authority_client = - match create_authority_client(node_manager, &request.authority, &request.identity).await? { - Ok(authority_client) => authority_client, - Err(direct_response) => { - return Ok(direct_response); - } - }; - - let member_identity = if let Ok(identifier) = Identifier::from_str(member_identity) { - identifier - } else { - warn!("Invalid member identity"); - return ControlApiHttpResponse::bad_request("Invalid member identity"); - }; + create_authority_client(node_manager, &request.authority, &request.identity).await?; let result = authority_client .add_member(context, member_identity, request.attributes) .await; match result { - Ok(_) => ControlApiHttpResponse::without_body(StatusCode::CREATED), + Ok(_) => Ok(ControlApiHttpResponse::without_body(StatusCode::CREATED)?), Err(error) => { warn!("Error adding member: {error}"); ControlApiHttpResponse::internal_error("Adding member failed") @@ -129,19 +123,11 @@ async fn handle_authority_member_list( context: &Context, node_manager: &Arc, body: Option>, -) -> ockam_core::Result { - let request: ListAuthorityMembersRequest = match common::parse_optional_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: ListAuthorityMembersRequest = common::parse_optional_request_body(body)?; let authority_client = - match create_authority_client(node_manager, &request.authority, &request.identity).await? { - Ok(authority_client) => authority_client, - Err(direct_response) => { - return Ok(direct_response); - } - }; + create_authority_client(node_manager, &request.authority, &request.identity).await?; let result = authority_client.list_members(context).await; match result { @@ -153,7 +139,7 @@ async fn handle_authority_member_list( attributes: attributes_entry.string_attributes(), }) .collect(); - ControlApiHttpResponse::with_body(StatusCode::OK, members) + Ok(ControlApiHttpResponse::with_body(StatusCode::OK, members)?) } Err(error) => { warn!("Error listing members: {error}"); @@ -185,39 +171,26 @@ async fn handle_authority_member_get( context: &Context, node_manager: &Arc, body: Option>, - resource_id: &str, -) -> ockam_core::Result { - let request: GetAuthorityMemberRequest = match common::parse_optional_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; + member_identity: &str, +) -> Result { + let request: GetAuthorityMemberRequest = common::parse_optional_request_body(body)?; + let member_identity = + common::parse_identifier(member_identity, "Invalid authority member identity")?; let authority_client = - match create_authority_client(node_manager, &request.authority, &request.identity).await? { - Ok(authority_client) => authority_client, - Err(direct_response) => { - return Ok(direct_response); - } - }; - - let member_identity = if let Ok(identifier) = Identifier::from_str(resource_id) { - identifier - } else { - warn!("Invalid member identity"); - return ControlApiHttpResponse::bad_request("Invalid member identity"); - }; + create_authority_client(node_manager, &request.authority, &request.identity).await?; let result = authority_client .show_member(context, &member_identity) .await; match result { - Ok(attributes_entry) => ControlApiHttpResponse::with_body( + Ok(attributes_entry) => Ok(ControlApiHttpResponse::with_body( StatusCode::OK, AuthorityMember { identity: member_identity.to_string(), attributes: attributes_entry.string_attributes(), }, - ), + )?), Err(error) => { //TODO: handle not found warn!("Error getting member: {error}"); @@ -249,32 +222,22 @@ async fn handle_authority_member_remove( context: &Context, node_manager: &Arc, body: Option>, - resource_id: &str, -) -> ockam_core::Result { - let request: RemoveAuthorityMemberRequest = match common::parse_optional_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; + member_identity: &str, +) -> Result { + let request: RemoveAuthorityMemberRequest = common::parse_optional_request_body(body)?; + let member_identity = + common::parse_identifier(member_identity, "Invalid authority member identity")?; let authority_client = - match create_authority_client(node_manager, &request.authority, &request.identity).await? { - Ok(authority_client) => authority_client, - Err(direct_response) => { - return Ok(direct_response); - } - }; - - let member_identity = if let Ok(identifier) = Identifier::from_str(resource_id) { - identifier - } else { - return ControlApiHttpResponse::bad_request("Invalid member identity"); - }; + create_authority_client(node_manager, &request.authority, &request.identity).await?; let result = authority_client .delete_member(context, &member_identity) .await; match result { - Ok(_) => ControlApiHttpResponse::without_body(StatusCode::NO_CONTENT), + Ok(_) => Ok(ControlApiHttpResponse::without_body( + StatusCode::NO_CONTENT, + )?), Err(error) => { //TODO: handle not found warn!("Error removing member: {error}"); diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/common.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/common.rs index 75667b15c41..d9f250cd518 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/common.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/common.rs @@ -1,5 +1,6 @@ use crate::control_api::http::ControlApiHttpResponse; use crate::control_api::protocol::common::Authority; +use crate::control_api::ControlApiError; use crate::nodes::NodeManager; use crate::orchestrator::project::Project; use crate::orchestrator::AuthorityNodeClient; @@ -14,77 +15,81 @@ pub async fn create_authority_client( node_manager: &Arc, authority: &Authority, caller_identifier: &Option, -) -> ockam_core::Result> { +) -> Result { let caller_identifier = if let Some(identity) = caller_identifier { - Identifier::from_str(identity)? + parse_identifier(identity, "selected node identity")? } else { node_manager.identifier() }; let authority_client: AuthorityNodeClient = match authority { Authority::Project { name: Some(name) } => { - if let Ok(project) = node_manager + match node_manager .cli_state .projects() .get_project_by_name(name) .await { - create_project_authority_with_project(node_manager, &project, &caller_identifier) + Ok(project) => { + create_project_authority_with_project( + node_manager, + &project, + &caller_identifier, + ) .await? - } else { - warn!("Project {name} not found"); - return Ok(Err(ControlApiHttpResponse::not_found("Project not found")?)); + } + Err(error) => { + warn!("Project {name} not found: {error:?}"); + return ControlApiHttpResponse::not_found("Project not found"); + } } } Authority::Project { name: None } => { - if let Ok(project) = node_manager + match node_manager .cli_state .projects() .get_default_project() .await { - create_project_authority_with_project(node_manager, &project, &caller_identifier) + Ok(project) => { + create_project_authority_with_project( + node_manager, + &project, + &caller_identifier, + ) .await? - } else { - warn!("No default project"); - return Ok(Err(ControlApiHttpResponse::bad_request( - "No default project", - )?)); + } + Err(error) => { + warn!("No default project: {error:?}"); + return ControlApiHttpResponse::bad_request("No default project"); + } } } Authority::Provided { route, identity } => { - let route = if let Ok(route) = MultiAddr::try_from(route.as_str()) { - route - } else { - warn!("Invalid authority route"); - return Ok(Err(ControlApiHttpResponse::bad_request( - "Invalid authority route", - )?)); + let route = match MultiAddr::try_from(route.as_str()) { + Ok(route) => route, + Err(error) => { + warn!("Invalid authority route: {error:?}"); + return ControlApiHttpResponse::bad_request("Invalid authority route"); + } }; - let identifier = if let Ok(identifier) = Identifier::from_str(identity) { - identifier - } else { - warn!("Invalid identity"); - return Ok(Err(ControlApiHttpResponse::bad_request( - "Invalid identity", - )?)); - }; + let identifier = parse_identifier(identity, "authority identity")?; node_manager .make_authority_node_client(&identifier, &route, &caller_identifier, None) .await? } }; - Ok(Ok(authority_client)) + Ok(authority_client) } pub async fn create_project_authority_with_project( node_manager: &Arc, project: &Project, caller_identifier: &Identifier, -) -> ockam_core::Result { +) -> Result { let is_project_admin = node_manager .cli_state .is_project_admin(caller_identifier, project) @@ -107,19 +112,19 @@ pub async fn create_project_authority_with_project( ) })?; - node_manager + Ok(node_manager .make_authority_node_client( &identifier, project.authority_multiaddr()?, caller_identifier, credential_retriever_creator, ) - .await + .await?) } pub fn parse_optional_request_body( body: Option>, -) -> Result> { +) -> Result { if let Some(body) = body { if body.is_empty() { Ok(T::default()) @@ -128,7 +133,7 @@ pub fn parse_optional_request_body( Ok(request) => Ok(request), Err(error) => { warn!("Invalid request body: {error:?}"); - Err(ControlApiHttpResponse::invalid_body()) + ControlApiHttpResponse::invalid_body() } } } @@ -138,18 +143,33 @@ pub fn parse_optional_request_body( } pub fn parse_request_body( body: Option>, -) -> Result> { +) -> Result { let request: T = if let Some(body) = body { match serde_json::from_slice(&body) { Ok(request) => request, Err(error) => { warn!("Invalid request body: {error:?}"); - return Err(ControlApiHttpResponse::invalid_body()); + return ControlApiHttpResponse::invalid_body(); } } } else { warn!("Missing request body"); - return Err(ControlApiHttpResponse::missing_body()); + return ControlApiHttpResponse::missing_body(); }; Ok(request) } + +pub fn parse_identifier( + identity: &str, + identity_parameter_name: &str, +) -> Result { + match Identifier::from_str(identity) { + Ok(identifier) => Ok(identifier), + Err(error) => { + warn!("Could not parse the {identity_parameter_name}: {error:?}"); + ControlApiHttpResponse::bad_request(&format!( + "Could not parse the {identity_parameter_name}. An identity starts with 'I' followed to hexadecimal characters" + )) + } + } +} diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/entrypoint.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/entrypoint.rs index cfbf65baa66..6fe7151d0cc 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/entrypoint.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/entrypoint.rs @@ -1,5 +1,6 @@ use crate::control_api::http::{ControlApiHttpRequest, ControlApiHttpResponse}; use crate::control_api::protocol::common::ErrorResponse; +use crate::control_api::ControlApiError; use crate::nodes::NodeManager; use crate::DefaultAddress; use http::{StatusCode, Uri}; @@ -67,7 +68,7 @@ impl Worker for HttpControlNodeApiBackend { let resource_kind = path[2].to_lowercase(); let resource_id = path.get(3).copied(); - let result: ockam_core::Result = match resource_kind.as_str() { + let result: Result = match resource_kind.as_str() { "tcp-inlet" => { self.handle_tcp_inlet(context, request.method.as_str(), resource_id, request.body) .await @@ -80,10 +81,10 @@ impl Worker for HttpControlNodeApiBackend { self.handle_relay(context, request.method.as_str(), resource_id, request.body) .await } - "ticket" => { - self.handle_ticket(context, request.method.as_str(), resource_id, request.body) - .await - } + "ticket" => Ok(self + .handle_ticket(context, request.method.as_str(), resource_id, request.body) + .await + .unwrap()), "authority-member" => { self.handle_authority_member( context, @@ -95,12 +96,18 @@ impl Worker for HttpControlNodeApiBackend { } _ => { warn!("Invalid resource kind: {resource_kind}"); - ControlApiHttpResponse::with_body( - StatusCode::BAD_REQUEST, - ErrorResponse { - message: "Unknown resource kind".to_string(), - }, - ) + let valid_resources = [ + "tcp-inlet", + "tcp-outlet", + "relay", + "ticket", + "authority-member", + ]; + let message = format!( + "Invalid resource kind: {resource_kind}. Possible: {}", + valid_resources.join(", ") + ); + ControlApiHttpResponse::bad_request(&message) } }; @@ -110,22 +117,40 @@ impl Worker for HttpControlNodeApiBackend { response } Err(error) => { - warn!("Error processing request: {error:?}"); - match error.code().kind { - // We make an assumption that every parsing error originates from the - // client; This is not necessarily always true, but it's a good approximation - Kind::Parse => ControlApiHttpResponse::with_body( - StatusCode::BAD_REQUEST, - ErrorResponse { - message: error.to_string(), - }, - )?, - _ => ControlApiHttpResponse::with_body( - StatusCode::INTERNAL_SERVER_ERROR, - ErrorResponse { - message: error.to_string(), - }, - )?, + match error { + ControlApiError::Response(response) => { + warn!( + "The API {} {} failed with status {}", + request.method, + request.uri, + StatusCode::try_from(response.status) + .map(|s| s.to_string()) + .unwrap_or_else(|_| response.status.to_string()) + ); + response + } + ControlApiError::OckamError(error) => { + warn!( + "The API {} {} failed with an expected error: {error:?}", + request.method, request.uri + ); + match error.code().kind { + // We make an assumption that every parsing error originates from the + // client; This is not necessarily always true, but it's a good approximation + Kind::Parse => ControlApiHttpResponse::with_body( + StatusCode::BAD_REQUEST, + ErrorResponse { + message: error.to_string(), + }, + )?, + _ => ControlApiHttpResponse::with_body( + StatusCode::INTERNAL_SERVER_ERROR, + ErrorResponse { + message: error.to_string(), + }, + )?, + } + } } } }; diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/inlet.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/inlet.rs index 62c2e74e6b0..806ba5e37f5 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/inlet.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/inlet.rs @@ -3,6 +3,7 @@ use crate::control_api::backend::entrypoint::HttpControlNodeApiBackend; use crate::control_api::http::ControlApiHttpResponse; use crate::control_api::protocol::inlet::{CreateInletRequest, InletKind, InletTls}; use crate::control_api::protocol::inlet::{InletStatus, UpdateInletRequest}; +use crate::control_api::ControlApiError; use crate::nodes::NodeManager; use http::StatusCode; use ockam_abac::{Action, Expr, PolicyExpression, ResourceName}; @@ -19,7 +20,9 @@ impl HttpControlNodeApiBackend { method: &str, resource_id: Option<&str>, body: Option>, - ) -> ockam_core::Result { + ) -> Result { + let resource_name = "tcp-inlet"; + let resource_name_identifier = "tcp_inlet_name"; match method { "PUT" => handle_tcp_inlet_create(context, &self.node_manager, body).await, "GET" => match resource_id { @@ -27,16 +30,25 @@ impl HttpControlNodeApiBackend { Some(id) => handle_tcp_inlet_get(&self.node_manager, id).await, }, "PATCH" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => handle_tcp_inlet_update(&self.node_manager, id, body).await, }, "DELETE" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => handle_tcp_inlet_delete(&self.node_manager, id).await, }, _ => { warn!("Invalid method: {method}"); - ControlApiHttpResponse::invalid_method() + ControlApiHttpResponse::invalid_method( + method, + vec!["PUT", "GET", "PATCH", "DELETE"], + ) } } } @@ -64,12 +76,8 @@ async fn handle_tcp_inlet_create( context: &Context, node_manager: &Arc, body: Option>, -) -> ockam_core::Result { - let request: CreateInletRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; - +) -> Result { + let request: CreateInletRequest = common::parse_request_body(body)?; let allow = match request.allow { None => None, Some(policy) => Some(PolicyExpression::try_from(policy.as_str())?), @@ -114,7 +122,25 @@ async fn handle_tcp_inlet_create( let tls_certificate_provider: Option = match request.tls { InletTls::None => None, - InletTls::ProjectTls => Some("/project/default/service/tls_certificate_provider".parse()?), + InletTls::ProjectTls => { + let default_project = match node_manager + .cli_state + .projects() + .get_default_project() + .await + { + Ok(project) => project, + Err(error) => { + warn!("Failed to get default project: {:?}", error); + return ControlApiHttpResponse::internal_error("Failed to get default project"); + } + }; + let default_project_name = default_project.name(); + Some( + format!("/project/{default_project_name}/service/tls_certificate_provider") + .parse()?, + ) + } InletTls::CustomTlsProvider { tls_certificate_provider, } => Some(tls_certificate_provider.parse()?), @@ -122,7 +148,10 @@ async fn handle_tcp_inlet_create( let authorized = match request.authorized { None => None, - Some(authorized) => Some(authorized.parse()?), + Some(authorized) => Some(common::parse_identifier( + authorized.as_str(), + "Invalid authorized identity", + )?), }; let result = node_manager @@ -147,9 +176,10 @@ async fn handle_tcp_inlet_create( ) .await; match result { - Ok(status) => { - ControlApiHttpResponse::with_body(StatusCode::CREATED, InletStatus::try_from(status)?) - } + Ok(status) => Ok(ControlApiHttpResponse::with_body( + StatusCode::CREATED, + InletStatus::try_from(status)?, + )?), Err(error) => { // TODO: specialize errors // name already exists @@ -184,14 +214,11 @@ async fn handle_tcp_inlet_update( node_manager: &Arc, resource_id: &str, body: Option>, -) -> ockam_core::Result { - let request: UpdateInletRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: UpdateInletRequest = common::parse_request_body(body)?; if node_manager.show_inlet(resource_id).await.is_none() { - return ControlApiHttpResponse::without_body(StatusCode::NOT_FOUND); + return ControlApiHttpResponse::not_found("Inlet not found"); } if let Some(allow) = request.allow { @@ -231,14 +258,12 @@ async fn handle_tcp_inlet_update( )] async fn handle_tcp_inlet_list( node_manager: &Arc, -) -> ockam_core::Result { +) -> Result { let mut inlets: Vec = Vec::new(); - for status in node_manager.list_inlets().await { inlets.push(InletStatus::try_from(status)?); } - - ControlApiHttpResponse::with_body(StatusCode::OK, inlets) + Ok(ControlApiHttpResponse::with_body(StatusCode::OK, inlets)?) } #[utoipa::path( @@ -258,10 +283,12 @@ async fn handle_tcp_inlet_list( async fn handle_tcp_inlet_delete( node_manager: &Arc, resource_id: &str, -) -> ockam_core::Result { +) -> Result { let result = node_manager.delete_inlet(resource_id).await; match result { - Ok(_) => ControlApiHttpResponse::without_body(StatusCode::NO_CONTENT), + Ok(_) => Ok(ControlApiHttpResponse::without_body( + StatusCode::NO_CONTENT, + )?), Err(error) => { warn!("Failed to delete tcp inlet: {:?}", error); ControlApiHttpResponse::internal_error("Failed to delete tcp inlet") @@ -287,19 +314,20 @@ async fn handle_tcp_inlet_delete( async fn handle_tcp_inlet_get( node_manager: &Arc, resource_id: &str, -) -> ockam_core::Result { +) -> Result { match node_manager.show_inlet(resource_id).await { - None => ControlApiHttpResponse::without_body(StatusCode::NOT_FOUND), - Some(status) => { - ControlApiHttpResponse::with_body(StatusCode::OK, InletStatus::try_from(status)?) - } + None => ControlApiHttpResponse::not_found("Inlet not found"), + Some(status) => Ok(ControlApiHttpResponse::with_body( + StatusCode::OK, + InletStatus::try_from(status)?, + )?), } } #[cfg(test)] mod test { use crate::control_api::http::{ControlApiHttpRequest, ControlApiHttpResponse}; - use crate::control_api::protocol::common::{ConnectionStatus, HostnamePort}; + use crate::control_api::protocol::common::{ConnectionStatus, ErrorResponse, HostnamePort}; use crate::control_api::protocol::inlet::{CreateInletRequest, InletStatus}; use crate::test_utils::start_manager_for_tests; use crate::DefaultAddress; @@ -429,7 +457,8 @@ mod test { let response: ControlApiHttpResponse = minicbor::decode(&encoded_response.into_vec())?; assert_eq!(response.status, 404); - assert!(response.body.is_empty()); + let body: ErrorResponse = serde_json::from_slice(response.body.as_slice()).unwrap(); + assert_eq!(body.message, "Inlet not found"); let request = ControlApiHttpRequest { method: "GET".to_string(), diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/outlet.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/outlet.rs index 9232e678e5f..440c832cd3e 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/outlet.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/outlet.rs @@ -5,6 +5,7 @@ use crate::control_api::protocol::common::ErrorResponse; use crate::control_api::protocol::outlet::{ CreateOutletRequest, OutletKind, OutletStatus, OutletTls, UpdateOutletRequest, }; +use crate::control_api::ControlApiError; use crate::nodes::models::portal::OutletAccessControl; use crate::nodes::NodeManager; use http::StatusCode; @@ -21,7 +22,9 @@ impl HttpControlNodeApiBackend { method: &str, resource_id: Option<&str>, body: Option>, - ) -> ockam_core::Result { + ) -> Result { + let resource_name = "tcp-outlet"; + let resource_name_identifier = "tcp_outlet_name"; match method { "PUT" => handle_tcp_outlet_create(context, &self.node_manager, body).await, "GET" => match resource_id { @@ -29,16 +32,25 @@ impl HttpControlNodeApiBackend { Some(id) => handle_tcp_outlet_get(&self.node_manager, id).await, }, "PATCH" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => handle_tcp_outlet_update(&self.node_manager, id, body).await, }, "DELETE" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => handle_tcp_outlet_delete(&self.node_manager, id).await, }, _ => { warn!("Invalid method: {method}"); - ControlApiHttpResponse::invalid_method() + ControlApiHttpResponse::invalid_method( + method, + vec!["PUT", "GET", "PATCH", "DELETE"], + ) } } } @@ -67,11 +79,8 @@ async fn handle_tcp_outlet_create( context: &Context, node_manager: &Arc, body: Option>, -) -> ockam_core::Result { - let request: CreateOutletRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: CreateOutletRequest = common::parse_request_body(body)?; let allow = OutletAccessControl::WithPolicyExpression(match request.allow { None => None, @@ -103,17 +112,18 @@ async fn handle_tcp_outlet_create( .await; match result { - Ok(outlet_status) => ControlApiHttpResponse::with_body( + Ok(outlet_status) => Ok(ControlApiHttpResponse::with_body( StatusCode::CREATED, OutletStatus::from(outlet_status), - ), + )?), Err(error) => match error.code().kind { - Kind::AlreadyExists => ControlApiHttpResponse::with_body( + Kind::AlreadyExists => Err(ControlApiHttpResponse::with_body( StatusCode::CONFLICT, ErrorResponse { message: error.to_string(), }, - ), + )? + .into()), _ => ControlApiHttpResponse::internal_error("Failed to create outlet"), }, } @@ -143,14 +153,11 @@ async fn handle_tcp_outlet_update( node_manager: &Arc, resource_id: &str, body: Option>, -) -> ockam_core::Result { - let request: UpdateOutletRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: UpdateOutletRequest = common::parse_request_body(body)?; if node_manager.show_outlet(&resource_id.into()).is_none() { - return ControlApiHttpResponse::without_body(StatusCode::NOT_FOUND); + return ControlApiHttpResponse::not_found("Outlet not found"); } if let Some(allow) = request.allow { @@ -190,13 +197,13 @@ async fn handle_tcp_outlet_update( )] async fn handle_tcp_outlet_list( node_manager: &Arc, -) -> ockam_core::Result { +) -> Result { let outlets: Vec = node_manager .list_outlets() .into_iter() .map(OutletStatus::from) .collect(); - ControlApiHttpResponse::with_body(StatusCode::OK, outlets) + Ok(ControlApiHttpResponse::with_body(StatusCode::OK, outlets)?) } #[utoipa::path( @@ -217,13 +224,14 @@ async fn handle_tcp_outlet_list( async fn handle_tcp_outlet_get( node_manager: &Arc, resource_id: &str, -) -> ockam_core::Result { +) -> Result { let result = node_manager.show_outlet(&Address::from_string(resource_id)); match result { - None => ControlApiHttpResponse::without_body(StatusCode::NOT_FOUND), - Some(status) => { - ControlApiHttpResponse::with_body(StatusCode::OK, OutletStatus::from(status)) - } + None => ControlApiHttpResponse::not_found("Outlet not found"), + Some(status) => Ok(ControlApiHttpResponse::with_body( + StatusCode::OK, + OutletStatus::from(status), + )?), } } @@ -245,17 +253,19 @@ async fn handle_tcp_outlet_get( async fn handle_tcp_outlet_delete( node_manager: &Arc, resource_id: &str, -) -> ockam_core::Result { +) -> Result { node_manager .delete_outlet(&Address::from_string(resource_id)) .await?; - ControlApiHttpResponse::without_body(StatusCode::NO_CONTENT) + Ok(ControlApiHttpResponse::without_body( + StatusCode::NO_CONTENT, + )?) } #[cfg(test)] mod test { use crate::control_api::http::{ControlApiHttpRequest, ControlApiHttpResponse}; - use crate::control_api::protocol::common::HostnamePort; + use crate::control_api::protocol::common::{ErrorResponse, HostnamePort}; use crate::control_api::protocol::outlet::{CreateOutletRequest, OutletKind, OutletStatus}; use crate::test_utils::start_manager_for_tests; use crate::DefaultAddress; @@ -374,7 +384,8 @@ mod test { let response: ControlApiHttpResponse = minicbor::decode(&encoded_response.into_vec())?; assert_eq!(response.status, 404); - assert!(response.body.is_empty()); + let body: ErrorResponse = serde_json::from_slice(response.body.as_slice()).unwrap(); + assert_eq!(body.message, "Outlet not found"); let request = ControlApiHttpRequest { method: "GET".to_string(), diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/relay.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/relay.rs index e6c57233ad5..19e3f8dd48e 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/relay.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/relay.rs @@ -2,6 +2,7 @@ use crate::control_api::backend::common; use crate::control_api::backend::entrypoint::HttpControlNodeApiBackend; use crate::control_api::http::ControlApiHttpResponse; use crate::control_api::protocol::relay::{CreateRelayRequest, RelayStatus}; +use crate::control_api::ControlApiError; use crate::nodes::models::relay::ReturnTiming; use crate::nodes::NodeManager; use http::StatusCode; @@ -18,7 +19,9 @@ impl HttpControlNodeApiBackend { method: &str, resource_id: Option<&str>, body: Option>, - ) -> ockam_core::Result { + ) -> Result { + let resource_name = "tcp-outlet"; + let resource_name_identifier = "tcp_outlet_name"; match method { "PUT" => handle_relay_create(context, &self.node_manager, body).await, "GET" => match resource_id { @@ -26,12 +29,15 @@ impl HttpControlNodeApiBackend { Some(id) => handle_relay_get(&self.node_manager, id).await, }, "DELETE" => match resource_id { - None => ControlApiHttpResponse::missing_resource_id(), + None => ControlApiHttpResponse::missing_resource_id( + resource_name, + resource_name_identifier, + ), Some(id) => handle_relay_delete(&self.node_manager, id).await, }, _ => { warn!("Invalid method: {method}"); - ControlApiHttpResponse::invalid_method() + ControlApiHttpResponse::invalid_method(method, vec!["PUT", "GET", "DELETE"]) } } } @@ -59,17 +65,15 @@ async fn handle_relay_create( context: &Context, node_manager: &Arc, body: Option>, -) -> ockam_core::Result { - let request: CreateRelayRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: CreateRelayRequest = common::parse_request_body(body)?; - let to = if let Ok(to) = MultiAddr::try_from(request.to.as_str()) { - to - } else { - warn!("Invalid 'to' address"); - return ControlApiHttpResponse::invalid_body(); + let to = match MultiAddr::try_from(request.to.as_str()) { + Ok(to) => to, + Err(error) => { + warn!("Invalid 'to' address: {error:?}"); + return ControlApiHttpResponse::invalid_body(); + } }; let name = request.name.unwrap_or_else(random_string); @@ -98,9 +102,10 @@ async fn handle_relay_create( ) .await; match result { - Ok(status) => { - ControlApiHttpResponse::with_body(StatusCode::CREATED, RelayStatus::from(status)) - } + Ok(status) => Ok(ControlApiHttpResponse::with_body( + StatusCode::CREATED, + RelayStatus::from(status), + )?), Err(error) => { // TODO: specialize errors // name already exists @@ -125,14 +130,14 @@ async fn handle_relay_create( )] async fn handle_relay_list( node_manager: &Arc, -) -> ockam_core::Result { +) -> Result { let mut inlets: Vec = Vec::new(); for status in node_manager.get_relays().await { inlets.push(RelayStatus::from(status)); } - ControlApiHttpResponse::with_body(StatusCode::OK, inlets) + Ok(ControlApiHttpResponse::with_body(StatusCode::OK, inlets)?) } #[utoipa::path( @@ -152,10 +157,12 @@ async fn handle_relay_list( async fn handle_relay_delete( node_manager: &Arc, resource_id: &str, -) -> ockam_core::Result { +) -> Result { let result = node_manager.delete_relay_impl(resource_id).await; match result { - Ok(_) => ControlApiHttpResponse::without_body(StatusCode::NO_CONTENT), + Ok(_) => Ok(ControlApiHttpResponse::without_body( + StatusCode::NO_CONTENT, + )?), Err(error) => { warn!("Failed to delete Relay: {:?}", error); ControlApiHttpResponse::internal_error("Failed to delete Relay") @@ -181,11 +188,12 @@ async fn handle_relay_delete( async fn handle_relay_get( node_manager: &Arc, resource_id: &str, -) -> ockam_core::Result { +) -> Result { match node_manager.show_relay(resource_id).await { - None => ControlApiHttpResponse::without_body(StatusCode::NOT_FOUND), - Some(status) => { - ControlApiHttpResponse::with_body(StatusCode::OK, RelayStatus::from(status)) - } + None => ControlApiHttpResponse::not_found("Relay not found"), + Some(status) => Ok(ControlApiHttpResponse::with_body( + StatusCode::OK, + RelayStatus::from(status), + )?), } } diff --git a/implementations/rust/ockam/ockam_api/src/control_api/backend/ticket.rs b/implementations/rust/ockam/ockam_api/src/control_api/backend/ticket.rs index 1667490a376..673405aa47a 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/backend/ticket.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/backend/ticket.rs @@ -2,18 +2,19 @@ use crate::authenticator::enrollment_tokens::TokenIssuer; use crate::authenticator::one_time_code::OneTimeCode; use crate::cli_state::{ExportedEnrollmentTicket, ProjectRoute}; use crate::control_api::backend::common; -use crate::control_api::backend::common::create_authority_client; +use crate::control_api::backend::common::{create_authority_client, parse_identifier}; use crate::control_api::backend::entrypoint::HttpControlNodeApiBackend; use crate::control_api::http::ControlApiHttpResponse; use crate::control_api::protocol::common::{ErrorResponse, HostnamePort, Project}; use crate::control_api::protocol::ticket::{ AuthorityInformation, CreateTicketRequest, EnrollTicketRequest, Ticket, }; +use crate::control_api::ControlApiError; use crate::enroll::enrollment::{EnrollStatus, Enrollment}; use crate::nodes::NodeManager; use crate::orchestrator::HasSecureClient; use http::StatusCode; -use ockam::identity::{Identifier, Identity, Vault}; +use ockam::identity::{Identity, Vault}; use ockam_core::errcode::{Kind, Origin}; use ockam_node::Context; use std::str::FromStr; @@ -27,13 +28,13 @@ impl HttpControlNodeApiBackend { method: &str, _resource_id: Option<&str>, body: Option>, - ) -> ockam_core::Result { + ) -> Result { match method { "PUT" => handle_ticket_create(context, &self.node_manager, body).await, "POST" => handle_ticket_enroll(context, &self.node_manager, body).await, _ => { warn!("Invalid method: {method}"); - ControlApiHttpResponse::invalid_method() + ControlApiHttpResponse::invalid_method(method, vec!["PUT", "POST"]) } } } @@ -61,24 +62,15 @@ async fn handle_ticket_create( context: &Context, node_manager: &Arc, body: Option>, -) -> ockam_core::Result { - let request: CreateTicketRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: CreateTicketRequest = common::parse_request_body(body)?; - let authority_client = match create_authority_client( + let authority_client = create_authority_client( node_manager, &request.project.to_project_authority().await?, &request.identity, ) - .await? - { - Ok(authority_client) => authority_client, - Err(direct_response) => { - return Ok(direct_response); - } - }; + .await?; let result = authority_client .create_token( @@ -92,14 +84,14 @@ async fn handle_ticket_create( match result { Ok(token) => { info!("Successfully created token"); - ControlApiHttpResponse::with_body( + Ok(ControlApiHttpResponse::with_body( StatusCode::CREATED, Ticket { encoded: create_encoded_ticket(node_manager, request.project, token) .await? .to_string(), }, - ) + )?) } Err(error) => { warn!("Error creating token: {error:?}"); @@ -227,14 +219,11 @@ async fn handle_ticket_enroll( context: &Context, node_manager: &Arc, body: Option>, -) -> ockam_core::Result { - let request: EnrollTicketRequest = match common::parse_request_body(body) { - Ok(value) => value, - Err(value) => return value, - }; +) -> Result { + let request: EnrollTicketRequest = common::parse_request_body(body)?; let caller_identifier = if let Some(identity) = request.identity { - Identifier::from_str(&identity)? + parse_identifier(&identity, "identity to enroll")? } else { node_manager.identifier() }; @@ -267,7 +256,8 @@ async fn handle_ticket_enroll( Origin::Api, Kind::Internal, "Project has no authority address", - )); + ) + .into()); }; let authority_route = project.authority_multiaddr().map(|m| m.to_string())?; @@ -300,34 +290,48 @@ async fn handle_ticket_enroll( }; if needs_restart { // enrolled, but the authority is not being used - ControlApiHttpResponse::with_body(StatusCode::ACCEPTED, authority_info) + Ok(ControlApiHttpResponse::with_body( + StatusCode::ACCEPTED, + authority_info, + )?) } else { // enrolled, and the authority is already being used - ControlApiHttpResponse::with_body(StatusCode::CREATED, authority_info) + Ok(ControlApiHttpResponse::with_body( + StatusCode::CREATED, + authority_info, + )?) } } EnrollStatus::AlreadyEnrolled => { // already enrolled - ControlApiHttpResponse::with_body(StatusCode::OK, authority_info) + Ok(ControlApiHttpResponse::with_body( + StatusCode::OK, + authority_info, + )?) } - EnrollStatus::UnexpectedStatus(error, status) => ControlApiHttpResponse::with_body( - StatusCode::BAD_GATEWAY, - ErrorResponse { - message: format!("Unexpected status: {} ({})", status, error), - }, - ), - EnrollStatus::FailedNoStatus(error) => ControlApiHttpResponse::with_body( + EnrollStatus::UnexpectedStatus(error, status) => { + Err(ControlApiHttpResponse::with_body( + StatusCode::BAD_GATEWAY, + ErrorResponse { + message: format!("Unexpected status: {} ({})", status, error), + }, + )? + .into()) + } + EnrollStatus::FailedNoStatus(error) => Err(ControlApiHttpResponse::with_body( StatusCode::BAD_GATEWAY, ErrorResponse { - message: format!("Communication error: {}", error), + message: format!("Authority Communication error: {}", error), }, - ), + )? + .into()), }, - Err(error) => ControlApiHttpResponse::with_body( + Err(error) => Err(ControlApiHttpResponse::with_body( StatusCode::BAD_GATEWAY, ErrorResponse { - message: format!("Communication error: {}", error), + message: format!("Authority Communication error: {}", error), }, - ), + )? + .into()), } } diff --git a/implementations/rust/ockam/ockam_api/src/control_api/frontend.rs b/implementations/rust/ockam/ockam_api/src/control_api/frontend.rs index 5f8d2fc5d86..08ac0e3a67e 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/frontend.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/frontend.rs @@ -224,9 +224,10 @@ impl HttpControlNodeApiFrontend { DefaultAddress::CONTROL_API ) } - NodeResolution::DirectConnection { suffix, port } => { + NodeResolution::DirectConnection { pattern, port } => { + let node_address = pattern.replace("{name}", node_name); format!( - "/dnsaddr/{node_name}{suffix}/tcp/{port}/secure/api/service/{}", + "/dnsaddr/{node_address}/tcp/{port}/secure/api/service/{}", DefaultAddress::CONTROL_API ) } @@ -373,7 +374,7 @@ impl HttpControlNodeApiFrontend { #[derive(Clone)] pub enum NodeResolution { Relay, - DirectConnection { suffix: String, port: u16 }, + DirectConnection { pattern: String, port: u16 }, } impl NodeManager { @@ -625,7 +626,7 @@ mod test { context, SocketAddr::from(([127, 0, 0, 1], 0)), NodeResolution::DirectConnection { - suffix: ".localhost".to_string(), + pattern: "{name}.localhost".to_string(), port: handle.bind_address.port(), }, "token".to_str(), @@ -662,7 +663,7 @@ mod test { context, SocketAddr::from(([127, 0, 0, 1], 0)), NodeResolution::DirectConnection { - suffix: ".localhost".to_string(), + pattern: "{name}.localhost".to_string(), port: 0, }, "token".to_str(), diff --git a/implementations/rust/ockam/ockam_api/src/control_api/http.rs b/implementations/rust/ockam/ockam_api/src/control_api/http.rs index 8a6d4633d84..757254ca943 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/http.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/http.rs @@ -1,4 +1,5 @@ use crate::control_api::protocol::common::ErrorResponse; +use crate::control_api::ControlApiError; use bytes::Bytes; use http::StatusCode; use http_body_util::Full; @@ -6,7 +7,6 @@ use minicbor::{CborLen, Decode, Encode}; use ockam_core::errcode::{Kind, Origin}; use ockam_core::Error; use serde::Serialize; -use tracing::error; #[derive(Debug, Encode, Decode, CborLen)] #[rustfmt::skip] @@ -47,67 +47,83 @@ impl ControlApiHttpResponse { }) } - pub fn invalid_body() -> ockam_core::Result { - Self::with_body( + pub fn invalid_body() -> Result { + Err(Self::with_body( StatusCode::BAD_REQUEST, ErrorResponse { message: "Invalid request body".to_string(), }, - ) + )? + .into()) } - pub fn missing_body() -> ockam_core::Result { - Self::with_body( + pub fn missing_body() -> Result { + Err(Self::with_body( StatusCode::BAD_REQUEST, ErrorResponse { message: "Missing request body".to_string(), }, - ) + )? + .into()) } - pub fn bad_request(message: &str) -> ockam_core::Result { - Self::with_body( + pub fn bad_request(message: &str) -> Result { + Err(Self::with_body( StatusCode::BAD_REQUEST, ErrorResponse { message: format!("Bad request: {message}"), }, - ) + )? + .into()) } - pub fn not_found(message: &str) -> ockam_core::Result { - Self::with_body( + pub fn not_found(message: &str) -> Result { + Err(Self::with_body( StatusCode::NOT_FOUND, ErrorResponse { message: message.to_string(), }, - ) + )? + .into()) } - pub fn internal_error(error: &str) -> ockam_core::Result { - Self::with_body( + pub fn internal_error(error: &str) -> Result { + Err(Self::with_body( StatusCode::INTERNAL_SERVER_ERROR, ErrorResponse { message: format!("Internal server error: {error}"), }, - ) + )? + .into()) } - pub fn invalid_method() -> ockam_core::Result { - Self::with_body( + pub fn invalid_method( + method: &str, + allowed_methods: Vec<&str>, + ) -> Result { + Err(Self::with_body( StatusCode::METHOD_NOT_ALLOWED, ErrorResponse { - message: "Method not allowed".to_string(), + message: format!( + "Invalid method {method} for this API. Supported methods are: {}", + allowed_methods.join(", ") + ), }, - ) + )? + .into()) } - pub fn missing_resource_id() -> ockam_core::Result { - Self::with_body( + pub fn missing_resource_id( + resource_name: &str, + resource_name_identifier: &str, + ) -> Result { + Err(Self::with_body( StatusCode::BAD_REQUEST, ErrorResponse { - message: "Missing resource ID".to_string(), + message: format!("Missing parameter {resource_name}. The HTTP path should be /{{node-name}}/{resource_name}/{{{resource_name_identifier}}}"), }, - ) + )? + .into()) } } diff --git a/implementations/rust/ockam/ockam_api/src/control_api/mod.rs b/implementations/rust/ockam/ockam_api/src/control_api/mod.rs index 473e37c432f..3117a863c5b 100644 --- a/implementations/rust/ockam/ockam_api/src/control_api/mod.rs +++ b/implementations/rust/ockam/ockam_api/src/control_api/mod.rs @@ -9,4 +9,41 @@ mod http; mod openapi; mod protocol; +use crate::cli_state::CliStateError; +use crate::control_api::http::ControlApiHttpResponse; pub use openapi::generate_schema; + +#[derive(Debug)] +pub enum ControlApiError { + Response(ControlApiHttpResponse), + OckamError(ockam_core::Error), +} +impl From for ControlApiError { + fn from(response: ControlApiHttpResponse) -> Self { + Self::Response(response) + } +} + +impl From for ControlApiError { + fn from(error: ockam_core::Error) -> Self { + Self::OckamError(error) + } +} + +impl From for ControlApiError { + fn from(error: CliStateError) -> Self { + Self::OckamError(ockam_core::Error::from(error)) + } +} + +impl From for ControlApiError { + fn from(error: ockam_multiaddr::Error) -> Self { + Self::OckamError(ockam_core::Error::from(error)) + } +} + +impl From for ControlApiError { + fn from(error: ockam_abac::ParseError) -> Self { + Self::OckamError(ockam_core::Error::from(error)) + } +} diff --git a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs index 3d258b19f83..e16be8d3364 100644 --- a/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs +++ b/implementations/rust/ockam/ockam_command/src/node/create/foreground.rs @@ -236,8 +236,8 @@ impl CreateCommand { ControlApiNodeResolution::Relay => NodeResolution::Relay, ControlApiNodeResolution::DirectConnection => { NodeResolution::DirectConnection { - suffix: configuration.node_resolution_suffix.clone(), - port: configuration.node_port, + pattern: configuration.node_resolution_pattern.clone(), + port: configuration.connection_node_port, } } }; diff --git a/implementations/rust/ockam/ockam_command/src/service/config.rs b/implementations/rust/ockam/ockam_command/src/service/config.rs index 910c650cd98..ed7b5b3e753 100644 --- a/implementations/rust/ockam/ockam_command/src/service/config.rs +++ b/implementations/rust/ockam/ockam_command/src/service/config.rs @@ -70,10 +70,14 @@ fn default_backend_policy() -> PolicyExpression { BooleanExpression(BooleanExpr::from_str("node_control_api_backend").unwrap()) } -fn default_node_port() -> u16 { +fn default_connection_node_port() -> u16 { 4100 } +fn default_node_resolution_pattern() -> String { + "{name}".to_string() +} + #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] pub struct ControlApiConfig { #[serde(default)] @@ -96,12 +100,15 @@ pub struct ControlApiConfig { pub(crate) http_bind_address: SocketAddr, /// Port to use when connecting to nodes. - #[serde(default = "default_node_port")] - pub(crate) node_port: u16, - - /// Suffix to use when connecting to nodes. - #[serde(default)] - pub(crate) node_resolution_suffix: String, + #[serde(default = "default_connection_node_port")] + pub(crate) connection_node_port: u16, + + /// Pattern to use when connecting to nodes. + /// {name} will be replaced with the node name. + /// When `name` is "node1", and the pattern is "my-{name}.example.com", the resulting address + /// will be "my-node1.example.com". + #[serde(default = "default_node_resolution_pattern")] + pub(crate) node_resolution_pattern: String, /// Authentication token for the control API. /// When undefined, the environment variable `OCKAM_CONTROL_API_AUTHENTICATION_TOKEN` will be used. diff --git a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/control_api.bats b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/control_api.bats index 56872ecb403..8d4d8180a4b 100644 --- a/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/control_api.bats +++ b/implementations/rust/ockam/ockam_command/tests/bats/orchestrator/control_api.bats @@ -65,7 +65,7 @@ teardown() { "{\"start_default_services\": true, \"startup_services\":{ \ \"control_api\":{\"authentication_token\": \"token\", \"backend\":false, \"frontend\":true, \"http_bind_address\":\"127.0.0.1:${api_port}\", \ \"node_resolution\":\"direct-connection\", \ - \"node_port\":${expected_connection_port} \ + \"connection_node_port\":${expected_connection_port} \ }}}" wait_for_port $api_port