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_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}