Skip to main content

redis_cloud/connectivity/
private_link.rs

1//! AWS `PrivateLink` connectivity operations
2//!
3//! This module provides AWS `PrivateLink` connectivity functionality for Redis Cloud,
4//! enabling secure, private connections from AWS VPCs to Redis Cloud databases.
5//!
6//! # Overview
7//!
8//! AWS `PrivateLink` allows you to connect to Redis Cloud from your AWS VPC without
9//! traversing the public internet. This provides enhanced security and potentially
10//! lower latency.
11//!
12//! # Features
13//!
14//! - **`PrivateLink` Management**: Create and retrieve `PrivateLink` configurations
15//! - **Principal Management**: Control which AWS principals can access the service
16//! - **Endpoint Scripts**: Get scripts to create endpoints in your AWS account
17//! - **Active-Active Support**: `PrivateLink` for CRDB (Active-Active) databases
18//!
19//! # Example Usage
20//!
21//! ```no_run
22//! use redis_cloud::{CloudClient, PrivateLinkHandler, PrivateLinkCreateRequest, PrincipalType};
23//!
24//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
25//! let client = CloudClient::builder()
26//!     .api_key("your-api-key")
27//!     .api_secret("your-api-secret")
28//!     .build()?;
29//!
30//! let handler = PrivateLinkHandler::new(client);
31//!
32//! // Create a PrivateLink
33//! let request = PrivateLinkCreateRequest {
34//!     share_name: "my-redis-share".to_string(),
35//!     principal: "123456789012".to_string(),
36//!     principal_type: PrincipalType::AwsAccount,
37//!     alias: Some("Production Account".to_string()),
38//! };
39//! let result = handler.create(123, &request).await?;
40//!
41//! // Get PrivateLink configuration
42//! let config = handler.get(123).await?;
43//! # Ok(())
44//! # }
45//! ```
46
47use crate::{CloudClient, Result};
48use serde::{Deserialize, Serialize};
49use serde_json::Value;
50// Note: Value is still needed for return types that use raw JSON responses
51
52// ============================================================================
53// Request/Response Types
54// ============================================================================
55
56/// Principal type for `PrivateLink` access control
57#[derive(Debug, Clone, Serialize, Deserialize)]
58#[serde(rename_all = "snake_case")]
59pub enum PrincipalType {
60    /// AWS account ID
61    AwsAccount,
62    /// AWS Organization
63    Organization,
64    /// AWS Organization Unit
65    OrganizationUnit,
66    /// AWS IAM Role
67    IamRole,
68    /// AWS IAM User
69    IamUser,
70    /// Service Principal
71    ServicePrincipal,
72}
73
74/// Request to create a `PrivateLink` configuration
75#[derive(Debug, Clone, Serialize, Deserialize)]
76#[serde(rename_all = "camelCase")]
77pub struct PrivateLinkCreateRequest {
78    /// Share name for the `PrivateLink` service (max 64 characters)
79    pub share_name: String,
80
81    /// AWS principal (account ID, role ARN, etc.)
82    pub principal: String,
83
84    /// Type of principal
85    #[serde(rename = "type")]
86    pub principal_type: PrincipalType,
87
88    /// Optional alias for the `PrivateLink`
89    #[serde(skip_serializing_if = "Option::is_none")]
90    pub alias: Option<String>,
91}
92
93/// Request to add a principal to `PrivateLink` access list
94#[derive(Debug, Clone, Serialize, Deserialize)]
95#[serde(rename_all = "camelCase")]
96pub struct PrivateLinkAddPrincipalRequest {
97    /// AWS principal (account ID, role ARN, etc.)
98    pub principal: String,
99
100    /// Type of principal
101    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
102    pub principal_type: Option<PrincipalType>,
103
104    /// Optional alias for the principal
105    #[serde(skip_serializing_if = "Option::is_none")]
106    pub alias: Option<String>,
107}
108
109/// Request to remove a principal from `PrivateLink` access list
110#[derive(Debug, Clone, Serialize, Deserialize)]
111#[serde(rename_all = "camelCase")]
112pub struct PrivateLinkRemovePrincipalRequest {
113    /// AWS principal to remove
114    pub principal: String,
115
116    /// Type of principal
117    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
118    pub principal_type: Option<PrincipalType>,
119
120    /// Alias of the principal
121    #[serde(skip_serializing_if = "Option::is_none")]
122    pub alias: Option<String>,
123}
124
125/// `PrivateLink` configuration response
126#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct PrivateLink {
129    /// `PrivateLink` status
130    #[serde(skip_serializing_if = "Option::is_none")]
131    pub status: Option<String>,
132
133    /// List of principals with access
134    #[serde(skip_serializing_if = "Option::is_none")]
135    pub principals: Option<Vec<PrivateLinkPrincipal>>,
136
137    /// AWS Resource Configuration ID
138    #[serde(skip_serializing_if = "Option::is_none")]
139    pub resource_configuration_id: Option<String>,
140
141    /// AWS Resource Configuration ARN
142    #[serde(skip_serializing_if = "Option::is_none")]
143    pub resource_configuration_arn: Option<String>,
144
145    /// RAM share ARN
146    #[serde(skip_serializing_if = "Option::is_none")]
147    pub share_arn: Option<String>,
148
149    /// Share name
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub share_name: Option<String>,
152
153    /// List of `PrivateLink` connections
154    #[serde(skip_serializing_if = "Option::is_none")]
155    pub connections: Option<Vec<PrivateLinkConnection>>,
156
157    /// List of databases accessible via `PrivateLink`
158    #[serde(skip_serializing_if = "Option::is_none")]
159    pub databases: Option<Vec<PrivateLinkDatabase>>,
160
161    /// Subscription ID
162    #[serde(skip_serializing_if = "Option::is_none")]
163    pub subscription_id: Option<i32>,
164
165    /// Region ID (for Active-Active)
166    #[serde(skip_serializing_if = "Option::is_none")]
167    pub region_id: Option<i32>,
168
169    /// Error message if any
170    #[serde(skip_serializing_if = "Option::is_none")]
171    pub error_message: Option<String>,
172}
173
174/// `PrivateLink` principal information
175#[derive(Debug, Clone, Serialize, Deserialize)]
176#[serde(rename_all = "camelCase")]
177pub struct PrivateLinkPrincipal {
178    /// AWS principal (account ID, role ARN, etc.)
179    #[serde(skip_serializing_if = "Option::is_none")]
180    pub principal: Option<String>,
181
182    /// Type of principal
183    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
184    pub principal_type: Option<String>,
185
186    /// Alias for the principal
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub alias: Option<String>,
189
190    /// Principal status
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub status: Option<String>,
193}
194
195/// `PrivateLink` connection information
196#[derive(Debug, Clone, Serialize, Deserialize)]
197#[serde(rename_all = "camelCase")]
198pub struct PrivateLinkConnection {
199    /// Association ID
200    #[serde(skip_serializing_if = "Option::is_none")]
201    pub association_id: Option<String>,
202
203    /// Connection ID
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub connection_id: Option<String>,
206
207    /// Connection type
208    #[serde(rename = "type", skip_serializing_if = "Option::is_none")]
209    pub connection_type: Option<String>,
210
211    /// Owner ID (AWS account)
212    #[serde(skip_serializing_if = "Option::is_none")]
213    pub owner_id: Option<String>,
214
215    /// Association date
216    #[serde(skip_serializing_if = "Option::is_none")]
217    pub association_date: Option<String>,
218}
219
220/// Database accessible via `PrivateLink`
221#[derive(Debug, Clone, Serialize, Deserialize)]
222#[serde(rename_all = "camelCase")]
223pub struct PrivateLinkDatabase {
224    /// Database ID
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub database_id: Option<i32>,
227
228    /// Database port
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub port: Option<i32>,
231
232    /// Resource link endpoint URL
233    #[serde(skip_serializing_if = "Option::is_none")]
234    pub resource_link_endpoint: Option<String>,
235}
236
237/// `PrivateLink` endpoint script response
238#[derive(Debug, Clone, Serialize, Deserialize)]
239#[serde(rename_all = "camelCase")]
240pub struct PrivateLinkEndpointScript {
241    /// AWS CLI/CloudFormation script
242    #[serde(skip_serializing_if = "Option::is_none")]
243    pub resource_endpoint_script: Option<String>,
244
245    /// Terraform AWS script
246    #[serde(skip_serializing_if = "Option::is_none")]
247    pub terraform_aws_script: Option<String>,
248}
249
250/// AWS `PrivateLink` handler
251///
252/// Manages AWS `PrivateLink` connectivity for Redis Cloud subscriptions.
253pub struct PrivateLinkHandler {
254    client: CloudClient,
255}
256
257impl PrivateLinkHandler {
258    /// Create a new `PrivateLink` handler
259    #[must_use]
260    pub fn new(client: CloudClient) -> Self {
261        Self { client }
262    }
263
264    /// Get `PrivateLink` configuration
265    ///
266    /// Gets the AWS `PrivateLink` configuration for a subscription.
267    ///
268    /// GET /subscriptions/{subscriptionId}/private-link
269    ///
270    /// # Arguments
271    ///
272    /// * `subscription_id` - The subscription ID
273    ///
274    /// # Returns
275    ///
276    /// Returns the `PrivateLink` configuration as JSON
277    pub async fn get(&self, subscription_id: i32) -> Result<Value> {
278        self.client
279            .get(&format!("/subscriptions/{subscription_id}/private-link"))
280            .await
281    }
282
283    /// Create a `PrivateLink`
284    ///
285    /// Creates a new AWS `PrivateLink` configuration for a subscription.
286    ///
287    /// POST /subscriptions/{subscriptionId}/private-link
288    ///
289    /// # Arguments
290    ///
291    /// * `subscription_id` - The subscription ID
292    /// * `request` - `PrivateLink` creation request
293    ///
294    /// # Returns
295    ///
296    /// Returns a task response that can be tracked for completion
297    pub async fn create(
298        &self,
299        subscription_id: i32,
300        request: &PrivateLinkCreateRequest,
301    ) -> Result<Value> {
302        self.client
303            .post(
304                &format!("/subscriptions/{subscription_id}/private-link"),
305                request,
306            )
307            .await
308    }
309
310    /// Add principals to `PrivateLink`
311    ///
312    /// Adds AWS principals (accounts, IAM roles, etc.) that can access the `PrivateLink`.
313    ///
314    /// POST /subscriptions/{subscriptionId}/private-link/principals
315    ///
316    /// # Arguments
317    ///
318    /// * `subscription_id` - The subscription ID
319    /// * `request` - Principal to add
320    ///
321    /// # Returns
322    ///
323    /// Returns the updated principal configuration
324    pub async fn add_principals(
325        &self,
326        subscription_id: i32,
327        request: &PrivateLinkAddPrincipalRequest,
328    ) -> Result<Value> {
329        self.client
330            .post(
331                &format!("/subscriptions/{subscription_id}/private-link/principals"),
332                request,
333            )
334            .await
335    }
336
337    /// Remove principals from `PrivateLink`
338    ///
339    /// Removes AWS principals from the `PrivateLink` access list.
340    ///
341    /// DELETE /subscriptions/{subscriptionId}/private-link/principals
342    ///
343    /// # Arguments
344    ///
345    /// * `subscription_id` - The subscription ID
346    /// * `request` - Principal to remove
347    ///
348    /// # Returns
349    ///
350    /// Returns confirmation of deletion
351    pub async fn remove_principals(
352        &self,
353        subscription_id: i32,
354        request: &PrivateLinkRemovePrincipalRequest,
355    ) -> Result<Value> {
356        self.client
357            .delete_with_body(
358                &format!("/subscriptions/{subscription_id}/private-link/principals"),
359                serde_json::to_value(request).unwrap_or_default(),
360            )
361            .await
362    }
363
364    /// Get endpoint creation script
365    ///
366    /// Gets a script to create the VPC endpoint in your AWS account.
367    ///
368    /// GET /subscriptions/{subscriptionId}/private-link/endpoint-script
369    ///
370    /// # Arguments
371    ///
372    /// * `subscription_id` - The subscription ID
373    ///
374    /// # Returns
375    ///
376    /// Returns the endpoint creation script
377    pub async fn get_endpoint_script(&self, subscription_id: i32) -> Result<Value> {
378        self.client
379            .get(&format!(
380                "/subscriptions/{subscription_id}/private-link/endpoint-script"
381            ))
382            .await
383    }
384
385    /// Delete `PrivateLink`
386    ///
387    /// Deletes the AWS `PrivateLink` configuration for a subscription.
388    ///
389    /// DELETE /subscriptions/{subscriptionId}/private-link
390    ///
391    /// # Arguments
392    ///
393    /// * `subscription_id` - The subscription ID
394    ///
395    /// # Returns
396    ///
397    /// Returns task information for tracking the deletion
398    pub async fn delete(&self, subscription_id: i32) -> Result<Value> {
399        self.client
400            .delete_raw(&format!("/subscriptions/{subscription_id}/private-link"))
401            .await
402    }
403
404    /// Get Active-Active `PrivateLink` configuration
405    ///
406    /// Gets the AWS `PrivateLink` configuration for an Active-Active (CRDB) subscription region.
407    ///
408    /// GET /subscriptions/{subscriptionId}/regions/{regionId}/private-link
409    ///
410    /// # Arguments
411    ///
412    /// * `subscription_id` - The subscription ID
413    /// * `region_id` - The region ID
414    ///
415    /// # Returns
416    ///
417    /// Returns the `PrivateLink` configuration for the region
418    pub async fn get_active_active(&self, subscription_id: i32, region_id: i32) -> Result<Value> {
419        self.client
420            .get(&format!(
421                "/subscriptions/{subscription_id}/regions/{region_id}/private-link"
422            ))
423            .await
424    }
425
426    /// Create Active-Active `PrivateLink`
427    ///
428    /// Creates a new AWS `PrivateLink` for an Active-Active (CRDB) subscription region.
429    ///
430    /// POST /subscriptions/{subscriptionId}/regions/{regionId}/private-link
431    ///
432    /// # Arguments
433    ///
434    /// * `subscription_id` - The subscription ID
435    /// * `region_id` - The region ID
436    /// * `request` - `PrivateLink` creation request
437    ///
438    /// # Returns
439    ///
440    /// Returns a task response
441    pub async fn create_active_active(
442        &self,
443        subscription_id: i32,
444        region_id: i32,
445        request: &PrivateLinkCreateRequest,
446    ) -> Result<Value> {
447        self.client
448            .post(
449                &format!("/subscriptions/{subscription_id}/regions/{region_id}/private-link"),
450                request,
451            )
452            .await
453    }
454
455    /// Add principals to Active-Active `PrivateLink`
456    ///
457    /// Adds AWS principals to an Active-Active `PrivateLink`.
458    ///
459    /// POST /subscriptions/{subscriptionId}/regions/{regionId}/private-link/principals
460    ///
461    /// # Arguments
462    ///
463    /// * `subscription_id` - The subscription ID
464    /// * `region_id` - The region ID
465    /// * `request` - Principal to add
466    ///
467    /// # Returns
468    ///
469    /// Returns the updated configuration
470    pub async fn add_principals_active_active(
471        &self,
472        subscription_id: i32,
473        region_id: i32,
474        request: &PrivateLinkAddPrincipalRequest,
475    ) -> Result<Value> {
476        self.client
477            .post(
478                &format!(
479                    "/subscriptions/{subscription_id}/regions/{region_id}/private-link/principals"
480                ),
481                request,
482            )
483            .await
484    }
485
486    /// Remove principals from Active-Active `PrivateLink`
487    ///
488    /// Removes AWS principals from an Active-Active `PrivateLink`.
489    ///
490    /// DELETE /subscriptions/{subscriptionId}/regions/{regionId}/private-link/principals
491    ///
492    /// # Arguments
493    ///
494    /// * `subscription_id` - The subscription ID
495    /// * `region_id` - The region ID
496    /// * `request` - Principal to remove
497    ///
498    /// # Returns
499    ///
500    /// Returns confirmation of deletion
501    pub async fn remove_principals_active_active(
502        &self,
503        subscription_id: i32,
504        region_id: i32,
505        request: &PrivateLinkRemovePrincipalRequest,
506    ) -> Result<Value> {
507        self.client
508            .delete_with_body(
509                &format!(
510                    "/subscriptions/{subscription_id}/regions/{region_id}/private-link/principals"
511                ),
512                serde_json::to_value(request).unwrap_or_default(),
513            )
514            .await
515    }
516
517    /// Get Active-Active endpoint creation script
518    ///
519    /// Gets a script to create the VPC endpoint for an Active-Active region.
520    ///
521    /// GET /subscriptions/{subscriptionId}/regions/{regionId}/private-link/endpoint-script
522    ///
523    /// # Arguments
524    ///
525    /// * `subscription_id` - The subscription ID
526    /// * `region_id` - The region ID
527    ///
528    /// # Returns
529    ///
530    /// Returns the endpoint creation script
531    pub async fn get_endpoint_script_active_active(
532        &self,
533        subscription_id: i32,
534        region_id: i32,
535    ) -> Result<Value> {
536        self.client
537            .get(&format!(
538                "/subscriptions/{subscription_id}/regions/{region_id}/private-link/endpoint-script"
539            ))
540            .await
541    }
542}