Skip to main content

alien_client_config/
client_config_ext.rs

1//! Extension trait for ClientConfig to provide environment-based configuration and impersonation
2
3use alien_client_core::{ErrorData, Result};
4use alien_core::{ClientConfig, ImpersonationConfig, Platform};
5use alien_error::AlienError;
6#[cfg(any(
7    feature = "aws",
8    feature = "gcp",
9    feature = "azure",
10    feature = "kubernetes"
11))]
12use alien_error::Context;
13use async_trait::async_trait;
14use std::collections::HashMap;
15
16/// Extension trait for ClientConfig providing environment-based configuration and cloud-agnostic impersonation
17#[async_trait]
18pub trait ClientConfigExt {
19    /// Create a platform configuration from environment variables based on the specified platform.
20    ///
21    /// # Examples
22    ///
23    /// ```rust,no_run
24    /// use alien_client_config::{ClientConfigExt};
25    /// use alien_core::{ClientConfig, Platform};
26    /// use std::collections::HashMap;
27    ///
28    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
29    /// let env_vars: HashMap<String, String> = std::env::vars().collect();
30    /// let aws_config = ClientConfig::from_env(Platform::Aws, &env_vars).await?;
31    /// let gcp_config = ClientConfig::from_env(Platform::Gcp, &env_vars).await?;
32    /// let azure_config = ClientConfig::from_env(Platform::Azure, &env_vars).await?;
33    /// # Ok(())
34    /// # }
35    /// ```
36    async fn from_env(
37        platform: Platform,
38        environment_variables: &HashMap<String, String>,
39    ) -> Result<Self>
40    where
41        Self: Sized;
42
43    /// Create a platform configuration from standard environment variables based on the specified platform.
44    ///
45    /// # Examples
46    ///
47    /// ```rust,no_run
48    /// use alien_client_config::{ClientConfigExt};
49    /// use alien_core::{ClientConfig, Platform};
50    ///
51    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
52    /// let aws_config = ClientConfig::from_std_env(Platform::Aws).await?;
53    /// let gcp_config = ClientConfig::from_std_env(Platform::Gcp).await?;
54    /// let azure_config = ClientConfig::from_std_env(Platform::Azure).await?;
55    /// # Ok(())
56    /// # }
57    /// ```
58    async fn from_std_env(platform: Platform) -> Result<Self>
59    where
60        Self: Sized;
61
62    /// Returns the platform enum for this configuration.
63    fn platform(&self) -> Platform;
64
65    /// Cloud-agnostic impersonation method
66    ///
67    /// # Examples
68    ///
69    /// ```rust,no_run
70    /// use alien_client_config::{ClientConfigExt};
71    /// use alien_core::{ClientConfig, ImpersonationConfig, AwsImpersonationConfig};
72    ///
73    /// # async fn example() -> Result<(), Box<dyn std::error::Error>> {
74    /// # let aws_config = ClientConfig::Test; // placeholder
75    /// let impersonation = ImpersonationConfig::Aws(AwsImpersonationConfig {
76    ///     role_arn: "arn:aws:iam::123456789012:role/MyRole".to_string(),
77    ///     session_name: None,
78    ///     duration_seconds: Some(3600),
79    ///     external_id: None,
80    ///     target_region: None,
81    /// });
82    ///
83    /// let impersonated_config = aws_config.impersonate(impersonation).await?;
84    /// # Ok(())
85    /// # }
86    /// ```
87    async fn impersonate(&self, config: ImpersonationConfig) -> Result<ClientConfig>;
88}
89
90#[async_trait]
91impl ClientConfigExt for ClientConfig {
92    async fn from_env(
93        platform: Platform,
94        environment_variables: &HashMap<String, String>,
95    ) -> Result<Self> {
96        match platform {
97            #[cfg(feature = "aws")]
98            Platform::Aws => {
99                use alien_aws_clients::AwsClientConfigExt;
100                let config = alien_aws_clients::AwsClientConfig::from_env(environment_variables)
101                    .await
102                    .context(ErrorData::InvalidClientConfig {
103                        message:
104                            "Failed to create AWS client configuration from environment variables"
105                                .to_string(),
106                        errors: None,
107                    })?;
108                Ok(ClientConfig::Aws(Box::new(config)))
109            }
110            #[cfg(not(feature = "aws"))]
111            Platform::Aws => Err(AlienError::new(ErrorData::InvalidClientConfig {
112                message: "AWS support is not enabled in this build".to_string(),
113                errors: None,
114            })),
115            #[cfg(feature = "gcp")]
116            Platform::Gcp => {
117                use alien_gcp_clients::GcpClientConfigExt;
118                let config = alien_gcp_clients::GcpClientConfig::from_env(environment_variables)
119                    .await
120                    .context(ErrorData::InvalidClientConfig {
121                        message:
122                            "Failed to create GCP client configuration from environment variables"
123                                .to_string(),
124                        errors: None,
125                    })?;
126                Ok(ClientConfig::Gcp(Box::new(config)))
127            }
128            #[cfg(not(feature = "gcp"))]
129            Platform::Gcp => Err(AlienError::new(ErrorData::InvalidClientConfig {
130                message: "GCP support is not enabled in this build".to_string(),
131                errors: None,
132            })),
133            #[cfg(feature = "azure")]
134            Platform::Azure => {
135                use alien_azure_clients::AzureClientConfigExt;
136                let config = alien_azure_clients::AzureClientConfig::from_env(
137                    environment_variables,
138                )
139                .await
140                .context(ErrorData::InvalidClientConfig {
141                    message:
142                        "Failed to create Azure client configuration from environment variables"
143                            .to_string(),
144                    errors: None,
145                })?;
146                Ok(ClientConfig::Azure(Box::new(config)))
147            }
148            #[cfg(not(feature = "azure"))]
149            Platform::Azure => Err(AlienError::new(ErrorData::InvalidClientConfig {
150                message: "Azure support is not enabled in this build".to_string(),
151                errors: None,
152            })),
153            #[cfg(feature = "kubernetes")]
154            Platform::Kubernetes => {
155                use alien_k8s_clients::KubernetesClientConfigExt;
156                let config = alien_k8s_clients::KubernetesClientConfig::from_env(environment_variables)
157                    .await
158                    .context(ErrorData::InvalidClientConfig {
159                        message: "Failed to create Kubernetes client configuration from environment variables".to_string(),
160                        errors: None,
161                    })?;
162                Ok(ClientConfig::Kubernetes(Box::new(config)))
163            }
164            #[cfg(not(feature = "kubernetes"))]
165            Platform::Kubernetes => Err(AlienError::new(ErrorData::InvalidClientConfig {
166                message: "Kubernetes support is not enabled in this build".to_string(),
167                errors: None,
168            })),
169            Platform::Test => Ok(ClientConfig::Test),
170            Platform::Local => {
171                // Local platform reads state directory from ALIEN_LOCAL_STATE_DIRECTORY
172                let state_directory = environment_variables
173                    .get("ALIEN_LOCAL_STATE_DIRECTORY")
174                    .cloned()
175                    .unwrap_or_else(|| "/tmp/alien-local".to_string());
176
177                Ok(ClientConfig::Local { state_directory })
178            }
179        }
180    }
181
182    async fn from_std_env(platform: Platform) -> Result<Self> {
183        let env_vars: HashMap<String, String> = std::env::vars().collect();
184        Self::from_env(platform, &env_vars).await
185    }
186
187    fn platform(&self) -> Platform {
188        match self {
189            #[cfg(feature = "aws")]
190            ClientConfig::Aws(_) => Platform::Aws,
191            #[cfg(feature = "gcp")]
192            ClientConfig::Gcp(_) => Platform::Gcp,
193            #[cfg(feature = "azure")]
194            ClientConfig::Azure(_) => Platform::Azure,
195            #[cfg(feature = "kubernetes")]
196            ClientConfig::Kubernetes(_) => Platform::Kubernetes,
197            #[cfg(feature = "kubernetes")]
198            ClientConfig::KubernetesCloud { .. } => Platform::Kubernetes,
199            ClientConfig::Test => Platform::Test,
200            ClientConfig::Local { .. } => Platform::Local,
201            // This should never be reached when no features are enabled,
202            // as the enum would be uninhabitable
203            #[allow(unreachable_patterns)]
204            _ => unreachable!("ClientConfig requires at least one platform feature to be enabled"),
205        }
206    }
207
208    async fn impersonate(&self, config: ImpersonationConfig) -> Result<ClientConfig> {
209        match (self, config) {
210            #[cfg(feature = "aws")]
211            (ClientConfig::Aws(aws_config), ImpersonationConfig::Aws(imp_config)) => {
212                use alien_aws_clients::AwsClientConfigExt;
213                let new_config = aws_config.impersonate(imp_config).await.map_err(|e| {
214                    AlienError::new(ErrorData::AuthenticationError {
215                        message: format!("AWS role impersonation failed: {}", e),
216                    })
217                })?;
218                Ok(ClientConfig::Aws(Box::new(new_config)))
219            }
220            #[cfg(feature = "gcp")]
221            (ClientConfig::Gcp(gcp_config), ImpersonationConfig::Gcp(imp_config)) => {
222                use alien_gcp_clients::GcpClientConfigExt;
223                let new_config = gcp_config.impersonate(imp_config).await.map_err(|e| {
224                    AlienError::new(ErrorData::AuthenticationError {
225                        message: format!("GCP service account impersonation failed: {}", e),
226                    })
227                })?;
228                Ok(ClientConfig::Gcp(Box::new(new_config)))
229            }
230            #[cfg(feature = "azure")]
231            (ClientConfig::Azure(azure_config), ImpersonationConfig::Azure(imp_config)) => {
232                use alien_azure_clients::AzureClientConfigExt;
233                let new_config = azure_config.impersonate(imp_config).await.map_err(|e| {
234                    AlienError::new(ErrorData::AuthenticationError {
235                        message: format!("Azure managed identity impersonation failed: {}", e),
236                    })
237                })?;
238                Ok(ClientConfig::Azure(Box::new(new_config)))
239            }
240            // Kubernetes doesn't support impersonation
241            #[cfg(feature = "kubernetes")]
242            (ClientConfig::Kubernetes(_), _) => Err(AlienError::new(ErrorData::InvalidInput {
243                message: "Kubernetes platform does not support impersonation".to_string(),
244                field_name: Some("impersonation_config".to_string()),
245            })),
246            _ => Err(AlienError::new(ErrorData::InvalidInput {
247                message: "Platform config and impersonation config types must match".to_string(),
248                field_name: Some("impersonation_config".to_string()),
249            })),
250        }
251    }
252}