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}