use std::time::Duration;
use crate::connection::EndpointPolicy;
use crate::documents::{PortForwardingSession, SsmDocument};
use crate::errors::Result;
use crate::session::{DocumentSpec, Session, SessionConfig, SessionManager};
#[derive(Debug, Clone)]
pub struct SessionBuilder {
region: Option<String>,
config: SessionConfig,
}
impl SessionBuilder {
pub fn new(target: impl Into<String>) -> Self {
Self {
region: None,
config: SessionConfig::new(target),
}
}
pub fn region(mut self, region: impl Into<String>) -> Self {
self.region = Some(region.into());
self
}
pub fn maybe_region(mut self, region: Option<impl Into<String>>) -> Self {
if let Some(region) = region {
self.region = Some(region.into());
}
self
}
pub fn document(mut self, document: impl SsmDocument) -> Self {
self.config.document = Some(DocumentSpec::new(&document));
self
}
pub fn port_forward(self, remote_port: u16) -> Self {
self.document(PortForwardingSession::new(remote_port))
}
pub fn reason(mut self, reason: impl Into<String>) -> Self {
self.config.reason = Some(reason.into());
self
}
pub fn ready_timeout(mut self, timeout: Duration) -> Self {
self.config.ready_timeout = timeout;
self
}
pub fn keepalive(mut self, interval: Duration, idle_timeout: Duration) -> Self {
self.config.heartbeat_interval = interval;
self.config.idle_timeout = idle_timeout;
self
}
pub fn payload_chunk_size(mut self, bytes: usize) -> Self {
self.config.payload_chunk_size = bytes;
self
}
pub fn output_buffer(mut self, messages: usize) -> Self {
self.config.output_buffer = messages;
self
}
pub fn endpoint_policy(mut self, policy: EndpointPolicy) -> Self {
self.config.endpoint_policy = policy;
self
}
pub fn config(&self) -> &SessionConfig {
&self.config
}
pub fn into_config(self) -> SessionConfig {
self.config
}
pub async fn start(self) -> Result<Session> {
let manager = match &self.region {
Some(region) => SessionManager::for_region(region.clone()).await?,
None => SessionManager::new().await?,
};
manager.start_session(self.config).await
}
pub async fn start_with(self, manager: &SessionManager) -> Result<Session> {
manager.start_session(self.config).await
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::documents::SessionType;
#[test]
fn port_forward_shorthand_matches_the_document() {
let config = SessionBuilder::new("i-abc")
.port_forward(3306)
.into_config();
let document = config.document.expect("a document must be set");
assert_eq!(document.name, "AWS-StartPortForwardingSession");
assert_eq!(document.parameters["portNumber"], vec!["3306".to_string()]);
assert_eq!(document.session_type, SessionType::Port);
}
#[test]
fn a_plain_builder_starts_a_shell() {
let config = SessionBuilder::new("i-abc").into_config();
assert!(config.document.is_none());
assert_eq!(config.session_type(), SessionType::StandardStream);
}
#[test]
fn region_is_recorded() {
assert_eq!(
SessionBuilder::new("i-abc").region("eu-west-1").region,
Some("eu-west-1".to_owned())
);
assert_eq!(SessionBuilder::new("i-abc").region, None);
}
#[test]
fn maybe_region_leaves_the_region_alone_when_none() {
let builder = SessionBuilder::new("i-abc")
.region("eu-west-1")
.maybe_region(Option::<String>::None);
assert_eq!(builder.region, Some("eu-west-1".to_owned()));
let builder = builder.maybe_region(Some("us-east-1"));
assert_eq!(builder.region, Some("us-east-1".to_owned()));
}
#[test]
fn tuning_knobs_reach_the_config() {
let config = SessionBuilder::new("i-abc")
.reason("audit")
.ready_timeout(Duration::from_secs(5))
.keepalive(Duration::from_secs(10), Duration::from_secs(45))
.payload_chunk_size(8192)
.output_buffer(64)
.endpoint_policy(EndpointPolicy::AllowAny)
.into_config();
assert_eq!(config.reason.as_deref(), Some("audit"));
assert_eq!(config.ready_timeout, Duration::from_secs(5));
assert_eq!(config.heartbeat_interval, Duration::from_secs(10));
assert_eq!(config.idle_timeout, Duration::from_secs(45));
assert_eq!(config.payload_chunk_size, 8192);
assert_eq!(config.output_buffer, 64);
assert_eq!(config.endpoint_policy, EndpointPolicy::AllowAny);
}
#[test]
fn the_last_document_wins() {
let config = SessionBuilder::new("i-abc")
.port_forward(1234)
.document(crate::documents::SshSession::new())
.into_config();
let document = config.document.unwrap();
assert_eq!(document.name, "AWS-StartSSHSession");
assert_eq!(document.session_type, SessionType::StandardStream);
}
}