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