solo_lib/sdk/qcloud/
cvm.rs

1//! # Qcloud CVM
2//!
3//! Begin with the [`go`] function
4
5use 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
126/// ### Solo GO! - Main function
127///
128/// Start to modify the security group rules.
129///
130/// This function is a basic implementation of the SDK. It is recommended to use
131/// this function directly.
132///
133/// ### Example
134/// ```rust
135/// use solo_lib::sdk::qcloud::{
136///     Secret,
137///     cvm::{SecurityGroup, go},
138/// };
139///
140/// #[tokio::main]
141/// async fn main() {
142///     let client = solo_lib::client::new();
143///     let secret = Secret {
144///         secret_id: "secret_id".to_string(),
145///         secret_key: "secret_key".to_string(),
146///     };
147///     let security_group = SecurityGroup {
148///         id: "security_group_id".to_string(),
149///         region: "security_group_region".to_string(),
150///     };
151///     let _result = go(
152///         &client,
153///         &security_group,
154///         &secret,
155///         "current_ipv4",
156///         "current_ipv6",
157///         &[
158///             "firewall_rule_one".to_string(),
159///             "firewall_rule_two".to_string(),
160///         ],
161///     )
162///     .await
163///     .unwrap();
164/// }
165/// ```
166pub 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
196/// ### SDK Implementation DescribeSecurityGroupPolicies
197///
198/// Note that this function is a single step of solo. Use it only if you
199/// would like to hook.
200pub 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
227/// ### SDK Process CompareSecurityGroupPolicies
228///
229/// Note that this function is a single step of solo. Use it only if you
230/// would like to hook.
231pub 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
269/// ### SDK Implementation ReplaceSecurityGroupPolicies
270///
271/// Note that this function is a single step of solo. Use it only if you
272/// would like to hook.
273pub 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}