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