1use std::{borrow::Borrow, collections::HashSet, result::Result::Ok};
6
7use anyhow::Result;
8use reqwest::Client;
9use serde::{Deserialize, Serialize};
10
11use super::util::{CommonResponse, Empty, Secret, parse_response};
12use crate::sdk::qcloud::util::{BasicRequest, MachineType, request_builder};
13
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct SecurityGroup {
16 pub id: String,
17 pub region: String,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct SecurityGroupPolicySet {
22 #[serde(rename = "Version", skip_serializing_if = "String::is_empty")]
23 pub version: String,
24 #[serde(rename = "Egress", skip_serializing_if = "Vec::is_empty")]
25 pub egress: Vec<SecurityGroupPolicy>,
26 #[serde(rename = "Ingress", skip_serializing_if = "Vec::is_empty")]
27 pub ingress: Vec<SecurityGroupPolicy>,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct SecurityGroupPolicy {
32 #[serde(rename = "PolicyIndex")]
33 pub policy_index: i32,
34 #[serde(rename = "Protocol", skip_serializing_if = "String::is_empty")]
35 pub protocol: String,
36 #[serde(rename = "Port", skip_serializing_if = "String::is_empty")]
37 pub port: String,
38 #[serde(
39 rename = "ServiceTemplate",
40 skip_serializing_if = "ServiceTemplateSpecification::is_empty"
41 )]
42 pub service_template: ServiceTemplateSpecification,
43 #[serde(rename = "CidrBlock", skip_serializing_if = "String::is_empty")]
44 pub cidr_block: String,
45 #[serde(
46 rename = "Ipv6CidrBlock",
47 skip_serializing_if = "String::is_empty"
48 )]
49 pub ipv6_cidr_block: String,
50 #[serde(
51 rename = "SecurityGroupId",
52 skip_serializing_if = "String::is_empty"
53 )]
54 pub security_group_id: String,
55 #[serde(
56 rename = "AddressTemplate",
57 skip_serializing_if = "AddressTemplateSpecification::is_empty"
58 )]
59 pub address_template: AddressTemplateSpecification,
60 #[serde(rename = "Action", skip_serializing_if = "String::is_empty")]
61 pub action: String,
62 #[serde(
63 rename = "PolicyDescription",
64 skip_serializing_if = "String::is_empty"
65 )]
66 pub policy_description: String,
67 #[serde(rename = "ModifyTime", skip_serializing_if = "String::is_empty")]
68 pub modify_time: String,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct AddressTemplateSpecification {
73 #[serde(rename = "AddressId", skip_serializing_if = "String::is_empty")]
74 pub address_id: String,
75 #[serde(
76 rename = "AddressGroupId",
77 skip_serializing_if = "String::is_empty"
78 )]
79 pub address_group_id: String,
80}
81
82impl AddressTemplateSpecification {
83 pub fn is_empty(&self) -> bool {
84 self.address_id.is_empty() && self.address_group_id.is_empty()
85 }
86}
87
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct ServiceTemplateSpecification {
90 #[serde(rename = "ServiceId", skip_serializing_if = "String::is_empty")]
91 pub service_id: String,
92 #[serde(
93 rename = "ServiceGroupId",
94 skip_serializing_if = "String::is_empty"
95 )]
96 pub service_group_id: String,
97}
98
99impl ServiceTemplateSpecification {
100 pub fn is_empty(&self) -> bool {
101 self.service_id.is_empty() && self.service_group_id.is_empty()
102 }
103}
104
105#[derive(Debug, Serialize, Deserialize)]
106pub struct DescribeSecurityGroupPoliciesRequest {
107 #[serde(rename = "SecurityGroupId")]
108 pub security_group_id: String,
109}
110
111#[derive(Debug, Serialize, Deserialize)]
112pub struct DescribeSecurityGroupPoliciesResponse {
113 #[serde(rename = "SecurityGroupPolicySet")]
114 pub security_group_policy_set: SecurityGroupPolicySet,
115}
116
117#[derive(Debug, Serialize, Deserialize)]
118pub struct ReplaceSecurityGroupPoliciesRequest {
119 #[serde(rename = "SecurityGroupId")]
120 pub security_group_id: String,
121 #[serde(rename = "SecurityGroupPolicySet")]
122 pub security_group_policy_set: SecurityGroupPolicySet,
123}
124
125pub async fn go(
166 client: &Client,
167 security_group: impl Borrow<SecurityGroup>,
168 secret: impl Borrow<Secret>,
169 current_ipv4: &str,
170 current_ipv6: &str,
171 matched_descriptions: &[String],
172) -> Result<()> {
173 let response =
174 list_rules(client, security_group.borrow(), secret.borrow()).await?;
175 let security_group_policy_set =
176 response.response.data.security_group_policy_set;
177 let (security_group_policy_set, require_update) = compare_rules(
178 &security_group_policy_set,
179 current_ipv4,
180 current_ipv6,
181 matched_descriptions,
182 );
183 if require_update {
184 modify_rules(
185 client,
186 security_group.borrow(),
187 secret.borrow(),
188 &security_group_policy_set,
189 )
190 .await?;
191 }
192 Ok(())
193}
194
195pub async fn list_rules(
200 client: &Client,
201 security_group: impl Borrow<SecurityGroup>,
202 secret: impl Borrow<Secret>,
203) -> Result<CommonResponse<DescribeSecurityGroupPoliciesResponse>> {
204 let security_group = security_group.borrow();
205
206 let request = DescribeSecurityGroupPoliciesRequest {
207 security_group_id: security_group.id.clone(),
208 };
209 let payload = serde_json::to_string(&request)?;
210 let basic_request = BasicRequest {
211 machine_type: MachineType::Cvm,
212 action: "DescribeSecurityGroupPolicies",
213 payload,
214 region: security_group.region.clone(),
215 secret: secret.borrow(),
216 };
217 let request = request_builder(client, basic_request)?;
218 let result = client.execute(request).await?;
219 let result = result.text().await?;
220
221 parse_response::<CommonResponse<DescribeSecurityGroupPoliciesResponse>>(
222 &result,
223 )
224}
225
226pub fn compare_rules(
231 security_group_policy_set: &SecurityGroupPolicySet,
232 current_ipv4: &str,
233 current_ipv6: &str,
234 matched_descriptions: &[String],
235) -> (SecurityGroupPolicySet, bool) {
236 let mut require_update = false;
237 let matched_set: HashSet<_> =
238 matched_descriptions.iter().map(|s| s.as_str()).collect();
239
240 let mut ingress = security_group_policy_set.ingress.clone();
241 for policy in &mut ingress {
242 if !matched_set.contains(policy.policy_description.as_str()) {
243 continue;
244 }
245 if policy.cidr_block.is_empty() {
246 if !current_ipv6.is_empty()
247 && policy.ipv6_cidr_block != current_ipv6
248 {
249 policy.ipv6_cidr_block = current_ipv6.to_string();
250 require_update = true;
251 }
252 } else if policy.cidr_block != current_ipv4 {
253 policy.cidr_block = current_ipv4.to_string();
254 require_update = true;
255 }
256 }
257
258 (
259 SecurityGroupPolicySet {
260 version: security_group_policy_set.version.clone(),
261 egress: security_group_policy_set.egress.clone(),
262 ingress,
263 },
264 require_update,
265 )
266}
267
268pub async fn modify_rules(
273 client: &Client,
274 security_group: impl Borrow<SecurityGroup>,
275 secret: impl Borrow<Secret>,
276 security_group_policy_set: &SecurityGroupPolicySet,
277) -> Result<CommonResponse<Empty>> {
278 let security_group = security_group.borrow();
279
280 let security_group_policy_set = SecurityGroupPolicySet {
281 egress: Vec::new(),
282 ..security_group_policy_set.clone()
283 };
284 let request = ReplaceSecurityGroupPoliciesRequest {
285 security_group_id: security_group.id.clone(),
286 security_group_policy_set,
287 };
288 let payload = serde_json::to_string(&request)?;
289 let basic_request = BasicRequest {
290 machine_type: MachineType::Cvm,
291 action: "ReplaceSecurityGroupPolicies",
292 payload,
293 region: security_group.region.clone(),
294 secret: secret.borrow(),
295 };
296 let request = request_builder(client, basic_request)?;
297 let result = client.execute(request).await?;
298 let result = result.text().await?;
299
300 parse_response::<CommonResponse<Empty>>(&result)
301}