Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add option to suppress stderr #57

Merged
merged 3 commits into from
Jan 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,319 changes: 630 additions & 689 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 0 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ name="testnet-deploy"

[dependencies]
async-recursion = "1.0.4"
async-trait = "0.1"
aws-config = "0.56.0"
aws-sdk-s3 = "0.29.0"
chrono = "0.4.31"
Expand Down Expand Up @@ -38,7 +37,4 @@ tokio = { version = "1.26", features = ["full"] }
tokio-stream = "0.1.14"

[dev-dependencies]
assert_fs = "~1.0"
httpmock = "0.6"
mockall = "0.11.3"
predicates = "2.0"
67 changes: 24 additions & 43 deletions src/ansible.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
//
// This SAFE Network Software is licensed under the BSD-3-Clause license.
// Please see the LICENSE file for more details.
use crate::error::{Error, Result};
use crate::CloudProvider;
use crate::{is_binary_on_path, run_external_command};
use crate::{
error::{Error, Result},
is_binary_on_path, run_external_command, CloudProvider,
};
use log::debug;
#[cfg(test)]
use mockall::automock;
use serde::Deserialize;
use std::collections::HashMap;
use std::net::IpAddr;
use std::path::PathBuf;
use std::{collections::HashMap, net::IpAddr, path::PathBuf};

/// Ansible has multiple 'binaries', e.g., `ansible-playbook`, `ansible-inventory` etc. that are
/// wrappers around the main `ansible` program. It would be a bit cumbersome to create a different
Expand Down Expand Up @@ -46,45 +43,13 @@ impl AnsibleBinary {
}
}

/// Provides an interface for running Ansible.
///
/// This trait exists for unit testing: it enables testing behaviour without actually calling the
/// Ansible process.
#[cfg_attr(test, automock)]
pub trait AnsibleRunnerInterface {
fn inventory_list(&self, inventory_path: PathBuf) -> Result<Vec<(String, IpAddr)>>;
fn run_playbook(
&self,
playbook_path: PathBuf,
inventory_path: PathBuf,
user: String,
extra_vars_document: Option<String>,
) -> Result<()>;
}

pub struct AnsibleRunner {
pub provider: CloudProvider,
pub working_directory_path: PathBuf,
pub ssh_sk_path: PathBuf,
pub vault_password_file_path: PathBuf,
}

impl AnsibleRunner {
pub fn new(
working_directory_path: PathBuf,
provider: CloudProvider,
ssh_sk_path: PathBuf,
vault_password_file_path: PathBuf,
) -> AnsibleRunner {
AnsibleRunner {
provider,
working_directory_path,
ssh_sk_path,
vault_password_file_path,
}
}
}

// The following three structs are utilities that are used to parse the output of the
// `ansible-inventory` command.
#[derive(Debug, Deserialize)]
Expand All @@ -100,11 +65,25 @@ struct Output {
_meta: Meta,
}

impl AnsibleRunnerInterface for AnsibleRunner {
impl AnsibleRunner {
pub fn new(
working_directory_path: PathBuf,
provider: CloudProvider,
ssh_sk_path: PathBuf,
vault_password_file_path: PathBuf,
) -> AnsibleRunner {
AnsibleRunner {
provider,
working_directory_path,
ssh_sk_path,
vault_password_file_path,
}
}

// This function is used to list the inventory of the ansible runner.
// It takes a PathBuf as an argument which represents the inventory path.
// It returns a Result containing a vector of tuples. Each tuple contains a string representing the name and the ansible host.
fn inventory_list(&self, inventory_path: PathBuf) -> Result<Vec<(String, IpAddr)>> {
pub fn inventory_list(&self, inventory_path: PathBuf) -> Result<Vec<(String, IpAddr)>> {
// Run the external command and store the output.
let output = run_external_command(
AnsibleBinary::AnsibleInventory.get_binary_path()?,
Expand All @@ -115,6 +94,7 @@ impl AnsibleRunnerInterface for AnsibleRunner {
"--list".to_string(),
],
true,
false,
)?;

// Debug the output of the inventory list.
Expand Down Expand Up @@ -144,7 +124,7 @@ impl AnsibleRunnerInterface for AnsibleRunner {
Ok(inventory)
}

fn run_playbook(
pub fn run_playbook(
&self,
playbook_path: PathBuf,
inventory_path: PathBuf,
Expand Down Expand Up @@ -175,6 +155,7 @@ impl AnsibleRunnerInterface for AnsibleRunner {
self.working_directory_path.clone(),
args,
false,
false,
)?;
Ok(())
}
Expand Down
21 changes: 3 additions & 18 deletions src/digital_ocean.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,9 @@
// Please see the LICENSE file for more details.

use crate::error::{Error, Result};
use async_trait::async_trait;
use log::debug;
#[cfg(test)]
use mockall::automock;
use reqwest::Client;
use std::net::Ipv4Addr;
use std::str::FromStr;
use std::{net::Ipv4Addr, str::FromStr};

pub const DIGITAL_OCEAN_API_BASE_URL: &str = "https://api.digitalocean.com";
pub const DIGITAL_OCEAN_API_PAGE_SIZE: usize = 200;
Expand All @@ -22,25 +18,14 @@ pub struct Droplet {
pub ip_address: Ipv4Addr,
}

/// Provides an interface for using the SSH client.
///
/// This trait exists for unit testing: it enables testing behaviour without actually calling the
/// ssh process.
#[cfg_attr(test, automock)]
#[async_trait]
pub trait DigitalOceanClientInterface {
async fn list_droplets(&self) -> Result<Vec<Droplet>>;
}

pub struct DigitalOceanClient {
pub base_url: String,
pub access_token: String,
pub page_size: usize,
}

#[async_trait]
impl DigitalOceanClientInterface for DigitalOceanClient {
async fn list_droplets(&self) -> Result<Vec<Droplet>> {
impl DigitalOceanClient {
pub async fn list_droplets(&self) -> Result<Vec<Droplet>> {
let client = Client::new();
let mut has_next_page = true;
let mut page = 1;
Expand Down
65 changes: 31 additions & 34 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,13 @@ pub mod setup;
pub mod ssh;
pub mod terraform;

#[cfg(test)]
mod tests;

use crate::{
ansible::{AnsibleRunner, AnsibleRunnerInterface},
ansible::AnsibleRunner,
error::{Error, Result},
rpc_client::{RpcClient, RpcClientInterface},
s3::{S3Repository, S3RepositoryInterface},
ssh::{SshClient, SshClientInterface},
terraform::{TerraformRunner, TerraformRunnerInterface},
rpc_client::RpcClient,
s3::S3Repository,
ssh::SshClient,
terraform::TerraformRunner,
};
use flate2::read::GzDecoder;
use indicatif::{ProgressBar, ProgressStyle};
Expand Down Expand Up @@ -310,39 +307,39 @@ impl TestnetDeployBuilder {
}

let testnet = TestnetDeploy::new(
Box::new(terraform_runner),
Box::new(ansible_runner),
Box::new(rpc_client),
Box::new(SshClient::new(ssh_secret_key_path)),
terraform_runner,
ansible_runner,
rpc_client,
SshClient::new(ssh_secret_key_path),
working_directory_path,
provider.clone(),
Box::new(S3Repository {}),
S3Repository {},
);

Ok(testnet)
}
}

pub struct TestnetDeploy {
pub terraform_runner: Box<dyn TerraformRunnerInterface>,
pub ansible_runner: Box<dyn AnsibleRunnerInterface>,
pub rpc_client: Box<dyn RpcClientInterface>,
pub ssh_client: Box<dyn SshClientInterface>,
pub terraform_runner: TerraformRunner,
pub ansible_runner: AnsibleRunner,
pub rpc_client: RpcClient,
pub ssh_client: SshClient,
pub working_directory_path: PathBuf,
pub cloud_provider: CloudProvider,
pub s3_repository: Box<dyn S3RepositoryInterface>,
pub s3_repository: S3Repository,
pub inventory_file_path: PathBuf,
}

impl TestnetDeploy {
pub fn new(
terraform_runner: Box<dyn TerraformRunnerInterface>,
ansible_runner: Box<dyn AnsibleRunnerInterface>,
rpc_client: Box<dyn RpcClientInterface>,
ssh_client: Box<dyn SshClientInterface>,
terraform_runner: TerraformRunner,
ansible_runner: AnsibleRunner,
rpc_client: RpcClient,
ssh_client: SshClient,
working_directory_path: PathBuf,
cloud_provider: CloudProvider,
s3_repository: Box<dyn S3RepositoryInterface>,
s3_repository: S3Repository,
) -> TestnetDeploy {
let inventory_file_path = working_directory_path
.join("ansible")
Expand Down Expand Up @@ -382,7 +379,7 @@ impl TestnetDeploy {
println!("Downloading the rpc client for safenode...");
let archive_name = "safenode_rpc_client-latest-x86_64-unknown-linux-musl.tar.gz";
get_and_extract_archive_from_s3(
&*self.s3_repository,
&self.s3_repository,
"sn-node-rpc-client",
archive_name,
&self.working_directory_path,
Expand Down Expand Up @@ -525,8 +522,7 @@ impl TestnetDeploy {
.par_iter()
.filter_map(|(vm_name, ip_address)| {
let ip_address = *ip_address;
let ssh_client_clone = self.ssh_client.clone_box();
match ssh_client_clone.run_script(
match self.ssh_client.run_script(
ip_address,
"safe",
PathBuf::from("scripts").join("get_peer_multiaddr.sh"),
Expand Down Expand Up @@ -569,7 +565,7 @@ impl TestnetDeploy {
do_clean(
name,
self.working_directory_path.clone(),
&*self.terraform_runner,
&self.terraform_runner,
vec![
"build".to_string(),
"genesis".to_string(),
Expand All @@ -583,7 +579,7 @@ impl TestnetDeploy {
/// Shared Helpers
///
pub async fn get_and_extract_archive_from_s3(
s3_repository: &dyn S3RepositoryInterface,
s3_repository: &S3Repository,
bucket_name: &str,
archive_bucket_path: &str,
dest_path: &Path,
Expand Down Expand Up @@ -622,7 +618,8 @@ pub fn run_external_command(
binary_path: PathBuf,
working_directory_path: PathBuf,
args: Vec<String>,
suppress_output: bool,
suppress_stdout: bool,
suppress_stderr: bool,
) -> Result<Vec<String>> {
let mut command = Command::new(binary_path.clone());
for arg in &args {
Expand All @@ -641,8 +638,8 @@ pub fn run_external_command(
let reader = BufReader::new(stdout);
for line in reader.lines() {
let line = line?;
if !suppress_output {
println!("{}", &line);
if !suppress_stdout {
println!("{line}");
}
output_lines.push(line);
}
Expand All @@ -652,8 +649,8 @@ pub fn run_external_command(
let reader = BufReader::new(stderr);
for line in reader.lines() {
let line = line?;
if !suppress_output {
println!("{}", &line);
if !suppress_stderr {
eprintln!("{line}");
}
output_lines.push(line);
}
Expand Down Expand Up @@ -685,7 +682,7 @@ pub fn is_binary_on_path(binary_name: &str) -> bool {
pub fn do_clean(
name: &str,
working_directory_path: PathBuf,
terraform_runner: &dyn TerraformRunnerInterface,
terraform_runner: &TerraformRunner,
inventory_types: Vec<String>,
) -> Result<()> {
terraform_runner.init()?;
Expand Down
17 changes: 12 additions & 5 deletions src/logs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@
// This SAFE Network Software is licensed under the BSD-3-Clause license.
// Please see the LICENSE file for more details.

use crate::error::{Error, Result};
use crate::s3::{S3Repository, S3RepositoryInterface};
use crate::{get_progress_bar, run_external_command, TestnetDeploy};
use crate::{
error::{Error, Result},
get_progress_bar, run_external_command,
s3::S3Repository,
TestnetDeploy,
};
use fs_extra::dir::{copy, remove, CopyOptions};
use log::debug;
use rayon::iter::{IntoParallelIterator, IntoParallelRefIterator, ParallelIterator};
Expand Down Expand Up @@ -87,6 +90,7 @@ impl TestnetDeploy {
PathBuf::from("."),
vec!["-R".to_string(), format!("{ip_address}")],
false,
false,
) {
println!("Failed to ssh-keygen {vm_name:?} : {ip_address} with err: {err:?}");
} else if let Err(err) =
Expand Down Expand Up @@ -118,6 +122,7 @@ impl TestnetDeploy {
PathBuf::from("."),
rsync_args_clone.clone(),
true,
false,
)?;

debug!("Finished rsync for for {vm_name:?} : {ip_address}");
Expand All @@ -134,11 +139,13 @@ impl TestnetDeploy {
println!("Running ripgrep with command: {rg_cmd}");

let progress_bar = get_progress_bar(all_node_inventory.len() as u64)?;
let ssh_client = self.ssh_client.clone_box();
let _failed_inventory = all_node_inventory
.par_iter()
.filter_map(|(vm_name, ip_address)| {
let op = match ssh_client.run_command(ip_address, "safe", &rg_cmd, true) {
let op = match self
.ssh_client
.run_command(ip_address, "safe", &rg_cmd, true)
{
Ok(output) => match Self::store_rg_output(&output, &log_abs_dest, vm_name) {
Ok(_) => None,
Err(err) => {
Expand Down
Loading
Loading