Skip to main content

solo_lib/sdk/qcloud/
cvm.rs

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