sn_testnet_deploy/
symlinked_antnode.rs

1// Copyright (c) 2023, MaidSafe.
2//
3// This SAFE Network Software is licensed under the BSD-3-Clause license.
4// Please see the LICENSE file for more details.
5
6use crate::{
7    ansible::{
8        extra_vars::build_symlinked_antnode_extra_vars,
9        inventory::{generate_environment_inventory, AnsibleInventoryType},
10        provisioning::{AnsibleProvisioner, ProvisionOptions},
11        AnsiblePlaybook, AnsibleRunner,
12    },
13    error::Result,
14    is_binary_on_path,
15    s3::S3Repository,
16    ssh::SshClient,
17    terraform::TerraformRunner,
18    BinaryOption, CloudProvider, EvmNetwork, LogFormat,
19};
20use std::{env, path::PathBuf, time::Duration};
21
22pub struct SymlinkedAntnodeDeployer {
23    ansible_provisioner: AnsibleProvisioner,
24    cloud_provider: CloudProvider,
25    name: String,
26    terraform_runner: TerraformRunner,
27    working_directory: PathBuf,
28}
29
30impl SymlinkedAntnodeDeployer {
31    pub fn new(
32        name: String,
33        cloud_provider: CloudProvider,
34        _s3_repository: S3Repository,
35        ansible_verbose_mode: bool,
36    ) -> Result<Self> {
37        let working_directory = env::current_dir()?;
38
39        let terraform_binary_path = if is_binary_on_path("tofu") {
40            PathBuf::from("tofu")
41        } else {
42            PathBuf::from("terraform")
43        };
44
45        let state_bucket_name = env::var("TERRAFORM_STATE_BUCKET_NAME")?;
46        let ssh_secret_key_path = PathBuf::from(env::var("SSH_KEY_PATH")?);
47        let vault_password_path = PathBuf::from(env::var("ANSIBLE_VAULT_PASSWORD_PATH")?);
48
49        let terraform_working_dir = match cloud_provider {
50            CloudProvider::Aws => {
51                working_directory.join("resources/terraform/symlinked-antnode/aws")
52            }
53            CloudProvider::DigitalOcean => {
54                working_directory.join("resources/terraform/symlinked-antnode/digital-ocean")
55            }
56        };
57
58        let terraform_runner = TerraformRunner::new(
59            terraform_binary_path,
60            terraform_working_dir,
61            cloud_provider,
62            &state_bucket_name,
63        )?;
64
65        let ansible_runner = AnsibleRunner::new(
66            50,
67            ansible_verbose_mode,
68            &name,
69            cloud_provider,
70            ssh_secret_key_path.clone(),
71            vault_password_path,
72            working_directory.join("resources/ansible"),
73        )?;
74
75        let ssh_client = SshClient::new(ssh_secret_key_path);
76        let ansible_provisioner =
77            AnsibleProvisioner::new(ansible_runner, cloud_provider, ssh_client);
78
79        Ok(Self {
80            ansible_provisioner,
81            cloud_provider,
82            name,
83            terraform_runner,
84            working_directory,
85        })
86    }
87
88    pub fn init(&self) -> Result<()> {
89        self.terraform_runner.init()
90    }
91
92    pub async fn create_infrastructure(
93        &self,
94        region: &str,
95        vm_size: Option<String>,
96        volume_size: Option<u16>,
97        use_custom_bin: bool,
98    ) -> Result<()> {
99        let workspaces = self.terraform_runner.workspace_list()?;
100        if !workspaces.contains(&self.name) {
101            self.terraform_runner.workspace_new(&self.name)?;
102        } else {
103            println!("Workspace {} already exists", self.name);
104            self.terraform_runner.workspace_select(&self.name)?;
105        }
106
107        let mut vars = vec![
108            ("region".to_string(), region.to_string()),
109            ("use_custom_bin".to_string(), use_custom_bin.to_string()),
110        ];
111
112        if let Some(size) = vm_size {
113            vars.push(("droplet_size".to_string(), size));
114        }
115
116        if let Some(size) = volume_size {
117            vars.push(("volume_size".to_string(), size.to_string()));
118        }
119
120        self.terraform_runner
121            .apply(vars, Some(vec!["dev.tfvars".to_string()]))?;
122
123        Ok(())
124    }
125
126    #[allow(clippy::too_many_arguments)]
127    pub async fn provision(
128        &self,
129        binary_option: &BinaryOption,
130        antnode_count: u16,
131        rewards_address: &str,
132        evm_network_type: EvmNetwork,
133        evm_data_payments_address: Option<String>,
134        evm_payment_token_address: Option<String>,
135        evm_rpc_url: Option<String>,
136        peer: Option<String>,
137        network_contacts_url: Option<String>,
138        network_id: Option<u8>,
139    ) -> Result<()> {
140        println!("Generating Ansible inventory...");
141        let base_inventory_path = self
142            .working_directory
143            .join("resources/ansible/inventory/dev_inventory_digital_ocean.yml");
144        let output_inventory_dir_path = self.working_directory.join("resources/ansible/inventory");
145        generate_environment_inventory(
146            &self.name,
147            &base_inventory_path,
148            &output_inventory_dir_path,
149        )?;
150
151        let build_custom_binaries = binary_option.should_provision_build_machine();
152        if build_custom_binaries {
153            println!("Building custom binaries...");
154            self.ansible_provisioner
155                .print_ansible_run_banner("Build Custom Binaries");
156
157            let output_inventory_dir_path =
158                self.working_directory.join("resources/ansible/inventory");
159
160            let provision_options = ProvisionOptions {
161                ant_version: None,
162                binary_option: binary_option.clone(),
163                chunk_size: None,
164                chunk_tracker_data_addresses: None,
165                chunk_tracker_services: None,
166                client_env_variables: None,
167                delayed_verifier_batch_size: None,
168                disable_nodes: false,
169                delayed_verifier_quorum_value: None,
170                start_delayed_verifier: false,
171                enable_logging: true,
172                enable_metrics: true,
173                start_random_verifier: false,
174                start_performance_verifier: false,
175                start_uploaders: false,
176                evm_data_payments_address: evm_data_payments_address.clone(),
177                evm_merkle_payments_address: None,
178                evm_network: evm_network_type.clone(),
179                evm_payment_token_address: evm_payment_token_address.clone(),
180                evm_rpc_url: evm_rpc_url.clone(),
181                expected_hash: None,
182                expected_size: None,
183                file_address: None,
184                full_cone_private_node_count: 0,
185                funding_wallet_secret_key: None,
186                gas_amount: None,
187                interval: Some(Duration::from_secs(5)),
188                log_format: Some(LogFormat::Default),
189                max_archived_log_files: 5,
190                max_log_files: 10,
191                max_uploads: None,
192                merkle: false,
193                name: self.name.clone(),
194                network_id,
195                network_dashboard_branch: None,
196                node_count: antnode_count,
197                node_env_variables: None,
198                output_inventory_dir_path: output_inventory_dir_path.clone(),
199                peer_cache_node_count: 0,
200                performance_verifier_batch_size: None,
201                port_restricted_cone_private_node_count: 0,
202                public_rpc: false,
203                random_verifier_batch_size: None,
204                repair_service_count: 0,
205                data_retrieval_service_count: 0,
206                rewards_address: Some(rewards_address.to_string()),
207                scan_frequency: None,
208                sleep_duration: None,
209                single_node_payment: false,
210                start_chunk_trackers: false,
211                start_data_retrieval: false,
212                symmetric_private_node_count: 0,
213                token_amount: None,
214                upload_batch_size: None,
215                upload_size: None,
216                upload_interval: None,
217                uploaders_count: None,
218                upnp_private_node_count: 0,
219                wallet_secret_keys: None,
220            };
221            self.ansible_provisioner
222                .build_autonomi_binaries(&provision_options, None)?;
223        }
224
225        let extra_vars = build_symlinked_antnode_extra_vars(
226            &self.cloud_provider.to_string(),
227            binary_option,
228            antnode_count,
229            rewards_address,
230            evm_network_type,
231            evm_data_payments_address,
232            evm_payment_token_address,
233            evm_rpc_url,
234            peer,
235            network_contacts_url,
236            network_id,
237            &self.name,
238        )?;
239        self.ansible_provisioner.ansible_runner.run_playbook(
240            AnsiblePlaybook::SymlinkedNodes,
241            AnsibleInventoryType::Nodes,
242            Some(extra_vars),
243        )?;
244
245        Ok(())
246    }
247
248    pub async fn destroy(&self) -> Result<()> {
249        self.terraform_runner.workspace_select(&self.name)?;
250        self.terraform_runner.destroy(None, None)?;
251        Ok(())
252    }
253}