1use 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_merkle_payments_address: Option<String>,
135 evm_payment_token_address: Option<String>,
136 evm_rpc_url: Option<String>,
137 peer: Option<String>,
138 network_contacts_url: Option<String>,
139 network_id: Option<u8>,
140 ) -> Result<()> {
141 println!("Generating Ansible inventory...");
142 let base_inventory_path = self
143 .working_directory
144 .join("resources/ansible/inventory/dev_inventory_digital_ocean.yml");
145 let output_inventory_dir_path = self.working_directory.join("resources/ansible/inventory");
146 generate_environment_inventory(
147 &self.name,
148 &base_inventory_path,
149 &output_inventory_dir_path,
150 )?;
151
152 let build_custom_binaries = binary_option.should_provision_build_machine();
153 if build_custom_binaries {
154 println!("Building custom binaries...");
155 self.ansible_provisioner
156 .print_ansible_run_banner("Build Custom Binaries");
157
158 let output_inventory_dir_path =
159 self.working_directory.join("resources/ansible/inventory");
160
161 let provision_options = ProvisionOptions {
162 ant_version: None,
163 binary_option: binary_option.clone(),
164 chunk_size: None,
165 chunk_tracker_data_addresses: None,
166 chunk_tracker_services: None,
167 client_env_variables: None,
168 delayed_verifier_batch_size: None,
169 disable_nodes: false,
170 delayed_verifier_quorum_value: None,
171 start_delayed_verifier: false,
172 enable_logging: true,
173 enable_metrics: true,
174 start_random_verifier: false,
175 start_performance_verifier: false,
176 start_uploaders: false,
177 evm_data_payments_address: evm_data_payments_address.clone(),
178 evm_merkle_payments_address: evm_merkle_payments_address.clone(),
179 evm_network: evm_network_type.clone(),
180 evm_payment_token_address: evm_payment_token_address.clone(),
181 evm_rpc_url: evm_rpc_url.clone(),
182 expected_hash: None,
183 expected_size: None,
184 file_address: None,
185 full_cone_private_node_count: 0,
186 funding_wallet_secret_key: None,
187 gas_amount: None,
188 interval: Some(Duration::from_secs(5)),
189 log_format: Some(LogFormat::Default),
190 max_archived_log_files: 5,
191 max_log_files: 10,
192 max_uploads: None,
193 merkle: false,
194 name: self.name.clone(),
195 network_id,
196 network_dashboard_branch: None,
197 node_count: antnode_count,
198 node_env_variables: None,
199 output_inventory_dir_path: output_inventory_dir_path.clone(),
200 peer_cache_node_count: 0,
201 performance_verifier_batch_size: None,
202 port_restricted_cone_private_node_count: 0,
203 public_rpc: false,
204 random_verifier_batch_size: None,
205 repair_service_count: 0,
206 data_retrieval_service_count: 0,
207 rewards_address: Some(rewards_address.to_string()),
208 scan_frequency: None,
209 sleep_duration: None,
210 single_node_payment: false,
211 start_chunk_trackers: false,
212 start_data_retrieval: false,
213 symmetric_private_node_count: 0,
214 token_amount: None,
215 upload_batch_size: None,
216 upload_size: None,
217 upload_interval: None,
218 uploaders_count: None,
219 upnp_private_node_count: 0,
220 wallet_secret_keys: None,
221 };
222 self.ansible_provisioner
223 .build_autonomi_binaries(&provision_options, None)?;
224 }
225
226 let extra_vars = build_symlinked_antnode_extra_vars(
227 &self.cloud_provider.to_string(),
228 binary_option,
229 antnode_count,
230 rewards_address,
231 evm_network_type,
232 evm_data_payments_address,
233 evm_merkle_payments_address,
234 evm_payment_token_address,
235 evm_rpc_url,
236 peer,
237 network_contacts_url,
238 network_id,
239 &self.name,
240 )?;
241 self.ansible_provisioner.ansible_runner.run_playbook(
242 AnsiblePlaybook::SymlinkedNodes,
243 AnsibleInventoryType::Nodes,
244 Some(extra_vars),
245 )?;
246
247 Ok(())
248 }
249
250 pub async fn destroy(&self) -> Result<()> {
251 self.terraform_runner.workspace_select(&self.name)?;
252 self.terraform_runner.destroy(None, None)?;
253 Ok(())
254 }
255}