Skip to main content

redis_cloud/connectivity/
transit_gateway.rs

1//! AWS Transit Gateway (TGW) attachment operations.
2//!
3//! Manages AWS Transit Gateway attachments and CIDR allow-lists so a Redis
4//! Cloud subscription can ride a hub-and-spoke topology you already
5//! operate, rather than terminating its own VPC peering connection.
6//!
7//! # When to use this module
8//!
9//! - You already run a Transit Gateway and want Redis Cloud subscriptions
10//!   to attach to it for centralized routing and segmentation.
11//! - You need to extend or update the CIDRs a subscription is allowed to
12//!   reach through its TGW attachment.
13//!
14//! For direct point-to-point AWS peering see
15//! [`crate::connectivity::vpc_peering`]; for endpoint-style PrivateLink
16//! connectivity see [`crate::connectivity::private_link`].
17//!
18//! # Endpoint surface
19//!
20//! Standard subscriptions:
21//!
22//! - `GET    /subscriptions/{subscriptionId}/transitGateways`
23//! - `POST   /subscriptions/{subscriptionId}/transitGateways/{tgwId}`
24//! - `DELETE /subscriptions/{subscriptionId}/transitGateways/{tgwId}`
25//! - `PUT    /subscriptions/{subscriptionId}/transitGateways/{tgwId}/attachment`
26//!
27//! Active-Active subscriptions expose the same surface under
28//! `/subscriptions/{subscriptionId}/regions/{regionId}/...`.
29//!
30//! # Errors
31//!
32//! All operations return [`crate::Result`]; transport, auth, and 4xx/5xx
33//! responses surface as the corresponding [`crate::CloudError`] variant.
34
35use crate::{CloudClient, Result};
36use serde::{Deserialize, Serialize};
37
38/// CIDR block definition
39#[derive(Debug, Clone, Serialize, Deserialize)]
40#[serde(rename_all = "camelCase")]
41pub struct Cidr {
42    /// CIDR notation address (e.g. `"10.0.0.0/16"`).
43    #[serde(skip_serializing_if = "Option::is_none")]
44    pub cidr_address: Option<String>,
45}
46
47/// Transit Gateway CIDRs update request
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "camelCase")]
50pub struct TgwUpdateCidrsRequest {
51    /// Optional. List of transit gateway attachment CIDRs.
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub cidrs: Option<Vec<Cidr>>,
54
55    /// Read-only on the response; populated by the server with the
56    /// operation type (e.g. `"UPDATE_TGW_CIDRS"`).
57    #[serde(skip_serializing_if = "Option::is_none")]
58    pub command_type: Option<String>,
59}
60
61/// Transit Gateway attachment request
62#[derive(Debug, Clone, Serialize, Deserialize)]
63#[serde(rename_all = "camelCase")]
64pub struct TgwAttachmentRequest {
65    /// AWS account ID
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub aws_account_id: Option<String>,
68
69    /// Transit Gateway ID
70    #[serde(skip_serializing_if = "Option::is_none")]
71    pub tgw_id: Option<String>,
72
73    /// CIDR blocks to route through the TGW
74    #[serde(skip_serializing_if = "Option::is_none")]
75    pub cidrs: Option<Vec<String>>,
76}
77
78/// Task state update response
79pub use crate::types::TaskStateUpdate;
80
81/// Transit Gateway attachment information
82#[derive(Debug, Clone, Serialize, Deserialize)]
83#[serde(rename_all = "camelCase")]
84pub struct TransitGatewayAttachment {
85    /// Attachment ID
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub id: Option<i32>,
88
89    /// AWS Transit Gateway UID
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub aws_tgw_uid: Option<String>,
92
93    /// AWS attachment UID
94    #[serde(skip_serializing_if = "Option::is_none")]
95    pub attachment_uid: Option<String>,
96
97    /// Attachment status
98    #[serde(skip_serializing_if = "Option::is_none")]
99    pub status: Option<String>,
100
101    /// AWS attachment status
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub attachment_status: Option<String>,
104
105    /// AWS account ID
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub aws_account_id: Option<String>,
108
109    /// CIDR blocks
110    #[serde(skip_serializing_if = "Option::is_none")]
111    pub cidrs: Option<Vec<CidrStatus>>,
112}
113
114/// CIDR block with status
115#[derive(Debug, Clone, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct CidrStatus {
118    /// CIDR address
119    #[serde(skip_serializing_if = "Option::is_none")]
120    pub cidr_address: Option<String>,
121
122    /// CIDR status
123    #[serde(skip_serializing_if = "Option::is_none")]
124    pub status: Option<String>,
125}
126
127/// Transit Gateway resource share invitation
128#[derive(Debug, Clone, Serialize, Deserialize)]
129#[serde(rename_all = "camelCase")]
130pub struct TransitGatewayInvitation {
131    /// Invitation ID
132    #[serde(skip_serializing_if = "Option::is_none")]
133    pub id: Option<i32>,
134
135    /// Invitation name
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub name: Option<String>,
138
139    /// AWS Resource share UID
140    #[serde(skip_serializing_if = "Option::is_none")]
141    pub resource_share_uid: Option<String>,
142
143    /// AWS account ID
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub aws_account_id: Option<String>,
146
147    /// Invitation status
148    #[serde(skip_serializing_if = "Option::is_none")]
149    pub status: Option<String>,
150
151    /// Date the resource was shared
152    #[serde(skip_serializing_if = "Option::is_none")]
153    pub shared_date: Option<String>,
154}
155
156/// Transit Gateway handler
157pub struct TransitGatewayHandler {
158    client: CloudClient,
159}
160
161impl TransitGatewayHandler {
162    /// Create a new Transit Gateway handler
163    #[must_use]
164    pub fn new(client: CloudClient) -> Self {
165        Self { client }
166    }
167
168    // ========================================================================
169    // Standard Transit Gateway Operations
170    // ========================================================================
171
172    /// Get Transit Gateway attachments
173    pub async fn get_attachments(&self, subscription_id: i32) -> Result<TaskStateUpdate> {
174        self.client
175            .get(&format!("/subscriptions/{subscription_id}/transitGateways"))
176            .await
177    }
178
179    /// Get Transit Gateway shared invitations
180    pub async fn get_shared_invitations(&self, subscription_id: i32) -> Result<TaskStateUpdate> {
181        self.client
182            .get(&format!(
183                "/subscriptions/{subscription_id}/transitGateways/invitations"
184            ))
185            .await
186    }
187
188    /// Accept Transit Gateway resource share
189    pub async fn accept_resource_share(
190        &self,
191        subscription_id: i32,
192        invitation_id: String,
193    ) -> Result<TaskStateUpdate> {
194        self.client
195            .put(
196                &format!(
197                    "/subscriptions/{subscription_id}/transitGateways/invitations/{invitation_id}/accept"
198                ),
199                &serde_json::json!({}),
200            )
201            .await
202    }
203
204    /// Reject Transit Gateway resource share
205    pub async fn reject_resource_share(
206        &self,
207        subscription_id: i32,
208        invitation_id: String,
209    ) -> Result<TaskStateUpdate> {
210        self.client
211            .put(
212                &format!(
213                    "/subscriptions/{subscription_id}/transitGateways/invitations/{invitation_id}/reject"
214                ),
215                &serde_json::json!({}),
216            )
217            .await
218    }
219
220    /// Delete Transit Gateway attachment
221    pub async fn delete_attachment(
222        &self,
223        subscription_id: i32,
224        attachment_id: String,
225    ) -> Result<TaskStateUpdate> {
226        self.client
227            .delete_typed(&format!(
228                "/subscriptions/{subscription_id}/transitGateways/{attachment_id}/attachment"
229            ))
230            .await
231    }
232
233    /// Create Transit Gateway attachment with `tgw_id` in path
234    pub async fn create_attachment_with_id(
235        &self,
236        subscription_id: i32,
237        tgw_id: &str,
238    ) -> Result<TaskStateUpdate> {
239        let request = TgwAttachmentRequest {
240            tgw_id: Some(tgw_id.to_string()),
241            aws_account_id: None,
242            cidrs: None,
243        };
244
245        self.client
246            .post(
247                &format!("/subscriptions/{subscription_id}/transitGateways/{tgw_id}/attachment"),
248                &request,
249            )
250            .await
251    }
252
253    /// Update Transit Gateway attachment CIDRs
254    pub async fn update_attachment_cidrs(
255        &self,
256        subscription_id: i32,
257        attachment_id: String,
258        request: &TgwAttachmentRequest,
259    ) -> Result<TaskStateUpdate> {
260        self.client
261            .put(
262                &format!(
263                    "/subscriptions/{subscription_id}/transitGateways/{attachment_id}/attachment"
264                ),
265                request,
266            )
267            .await
268    }
269
270    // ========================================================================
271    // Active-Active Transit Gateway Operations
272    // ========================================================================
273
274    /// Get Active-Active Transit Gateway attachments for a region
275    pub async fn get_attachments_active_active(
276        &self,
277        subscription_id: i32,
278        region_id: i32,
279    ) -> Result<TaskStateUpdate> {
280        self.client
281            .get(&format!(
282                "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways"
283            ))
284            .await
285    }
286
287    /// Get Active-Active Transit Gateway shared invitations for a region
288    pub async fn get_shared_invitations_active_active(
289        &self,
290        subscription_id: i32,
291        region_id: i32,
292    ) -> Result<TaskStateUpdate> {
293        self.client
294            .get(&format!(
295                "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways/invitations"
296            ))
297            .await
298    }
299
300    /// Accept Active-Active Transit Gateway resource share
301    pub async fn accept_resource_share_active_active(
302        &self,
303        subscription_id: i32,
304        region_id: i32,
305        invitation_id: String,
306    ) -> Result<TaskStateUpdate> {
307        self.client
308            .put(
309                &format!(
310                    "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways/invitations/{invitation_id}/accept"
311                ),
312                &serde_json::json!({}),
313            )
314            .await
315    }
316
317    /// Reject Active-Active Transit Gateway resource share
318    pub async fn reject_resource_share_active_active(
319        &self,
320        subscription_id: i32,
321        region_id: i32,
322        invitation_id: String,
323    ) -> Result<TaskStateUpdate> {
324        self.client
325            .put(
326                &format!(
327                    "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways/invitations/{invitation_id}/reject"
328                ),
329                &serde_json::json!({}),
330            )
331            .await
332    }
333
334    /// Delete Active-Active Transit Gateway attachment
335    pub async fn delete_attachment_active_active(
336        &self,
337        subscription_id: i32,
338        region_id: i32,
339        tgw_id: String,
340    ) -> Result<TaskStateUpdate> {
341        self.client.delete_typed(&format!(
342                "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways/{tgw_id}/attachment"
343            )).await
344    }
345
346    /// Create Active-Active Transit Gateway attachment
347    pub async fn create_attachment_active_active(
348        &self,
349        subscription_id: i32,
350        region_id: i32,
351        tgw_id: &str,
352        request: &TgwAttachmentRequest,
353    ) -> Result<TaskStateUpdate> {
354        self.client
355            .post(
356                &format!(
357                    "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways/{tgw_id}/attachment"
358                ),
359                request,
360            )
361            .await
362    }
363
364    /// Update Active-Active Transit Gateway attachment CIDRs
365    pub async fn update_attachment_cidrs_active_active(
366        &self,
367        subscription_id: i32,
368        region_id: i32,
369        tgw_id: String,
370        request: &TgwAttachmentRequest,
371    ) -> Result<TaskStateUpdate> {
372        self.client
373            .put(
374                &format!(
375                    "/subscriptions/{subscription_id}/regions/{region_id}/transitGateways/{tgw_id}/attachment"
376                ),
377                request,
378            )
379            .await
380    }
381}