1use std::{borrow::Borrow, collections::HashSet, result::Result::Ok};
6
7use anyhow::Result;
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10
11use super::util::{
12 BasicRequest, CommonResponse, Empty, MachineType, Secret, parse_response,
13 request_builder,
14};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct Instance {
19 pub id: String,
20 pub region: String,
21}
22
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct FirewallRuleInfo {
25 #[serde(rename = "AppType", skip_serializing_if = "Option::is_none")]
26 pub app_type: Option<String>,
27
28 #[serde(rename = "Protocol")]
29 pub protocol: String,
30
31 #[serde(rename = "Port")]
32 pub port: String,
33
34 #[serde(rename = "CidrBlock")]
35 pub cidr_block: String,
36
37 #[serde(rename = "Action")]
38 pub action: String,
39
40 #[serde(rename = "FirewallRuleDescription")]
41 pub firewall_rule_description: String,
42}
43
44#[derive(Debug, Clone, Serialize, Deserialize)]
45struct DescribeFirewallRulesRequest {
46 #[serde(rename = "InstanceId")]
47 instance_id: String,
48 #[serde(rename = "Offset")]
49 offset: i32,
50 #[serde(rename = "Limit")]
51 limit: i32,
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct DescribeFirewallRulesResponse {
56 #[serde(rename = "FirewallRuleSet")]
57 pub firewall_rule_set: Vec<FirewallRuleInfo>,
58 #[serde(rename = "FirewallVersion")]
59 pub firewall_version: i32,
60 #[serde(rename = "TotalCount")]
61 pub total_count: i32,
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65struct ModifyFirewallRulesRequest {
66 #[serde(rename = "InstanceId")]
67 instance_id: String,
68 #[serde(rename = "FirewallRules")]
69 firewall_rules: Vec<FirewallRuleInfo>,
70}
71
72pub async fn go(
112 client: &Client,
113 instance: impl Borrow<Instance>,
114 secret: impl Borrow<Secret>,
115 current_ip: &str,
116 matched_descriptions: &[String],
117) -> Result<()> {
118 let response =
119 list_rules(client, instance.borrow(), secret.borrow()).await?;
120 let firewall_rules = &response.response.data.firewall_rule_set;
121 let (firewall_rules, require_update) =
122 compare_rules(firewall_rules, current_ip, matched_descriptions);
123 if require_update {
124 modify_rules(
125 client,
126 instance.borrow(),
127 secret.borrow(),
128 &firewall_rules,
129 )
130 .await?;
131 }
132 Ok(())
133}
134
135pub async fn list_rules(
140 client: &Client,
141 instance: impl Borrow<Instance>,
142 secret: impl Borrow<Secret>,
143) -> Result<CommonResponse<DescribeFirewallRulesResponse>> {
144 let instance = instance.borrow();
145
146 let request = DescribeFirewallRulesRequest {
147 instance_id: instance.id.clone(),
148 offset: 0,
149 limit: 100,
150 };
151 let payload = serde_json::to_string(&request)?;
152 let basic_request = BasicRequest {
153 machine_type: MachineType::Lighthouse,
154 action: "DescribeFirewallRules",
155 payload,
156 region: instance.region.clone(),
157 secret: secret.borrow(),
158 };
159 let request = request_builder(client, basic_request)?;
160 let result = client.execute(request).await?;
161 let result = result.text().await?;
162
163 parse_response::<CommonResponse<DescribeFirewallRulesResponse>>(&result)
164}
165
166pub fn compare_rules(
171 firewall_rules: &[FirewallRuleInfo],
172 current_ip: &str,
173 matched_descriptions: &[String],
174) -> (Vec<FirewallRuleInfo>, bool) {
175 let matched_set: HashSet<&str> =
176 matched_descriptions.iter().map(|s| s.as_str()).collect();
177
178 let mut require_update = false;
179 let modified_rules: Vec<FirewallRuleInfo> = firewall_rules
180 .iter()
181 .map(|rule| {
182 if matched_set.contains(rule.firewall_rule_description.as_str()) {
183 if rule.cidr_block != current_ip {
184 require_update = true;
185 FirewallRuleInfo {
186 cidr_block: current_ip.into(),
187 ..rule.clone()
188 }
189 } else {
190 rule.clone()
191 }
192 } else {
193 rule.clone()
194 }
195 })
196 .collect();
197
198 (modified_rules, require_update)
199}
200
201pub async fn modify_rules(
206 client: &Client,
207 instance: impl Borrow<Instance>,
208 secret: impl Borrow<Secret>,
209 firewall_rules: &[FirewallRuleInfo],
210) -> Result<CommonResponse<Empty>> {
211 let instance = instance.borrow();
212
213 let firewall_rules: Vec<FirewallRuleInfo> = firewall_rules
214 .iter()
215 .cloned()
216 .map(|mut rule| {
217 rule.app_type = None;
218 rule
219 })
220 .collect();
221
222 let request = ModifyFirewallRulesRequest {
223 instance_id: instance.id.clone(),
224 firewall_rules,
225 };
226
227 let payload = serde_json::to_string(&request)?;
228 let basic_request = BasicRequest {
229 machine_type: MachineType::Lighthouse,
230 action: "ModifyFirewallRules",
231 payload,
232 region: instance.region.clone(),
233 secret: secret.borrow(),
234 };
235
236 let request = request_builder(client, basic_request)?;
237 let result = client.execute(request).await?;
238 let result = result.text().await?;
239
240 parse_response::<CommonResponse<Empty>>(&result)
241}