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::Machines => Err(AlienError::new(ErrorData::InvalidClientConfig {
170                message: "Machines deployments do not use local cloud client configuration"
171                    .to_string(),
172                errors: None,
173            })),
174            Platform::Test => Ok(ClientConfig::Test),
175            Platform::Local => {
176                // Local platform reads state directory from ALIEN_LOCAL_STATE_DIRECTORY
177                let state_directory = environment_variables
178                    .get("ALIEN_LOCAL_STATE_DIRECTORY")
179                    .cloned()
180                    .unwrap_or_else(|| "/tmp/alien-local".to_string());
181
182                Ok(ClientConfig::Local { state_directory })
183            }
184        }
185    }
186
187    async fn from_std_env(platform: Platform) -> Result<Self> {
188        let env_vars: HashMap<String, String> = std::env::vars().collect();
189        Self::from_env(platform, &env_vars).await
190    }
191
192    fn platform(&self) -> Platform {
193        match self {
194            #[cfg(feature = "aws")]
195            ClientConfig::Aws(_) => Platform::Aws,
196            #[cfg(feature = "gcp")]
197            ClientConfig::Gcp(_) => Platform::Gcp,
198            #[cfg(feature = "azure")]
199            ClientConfig::Azure(_) => Platform::Azure,
200            #[cfg(feature = "kubernetes")]
201            ClientConfig::Kubernetes(_) => Platform::Kubernetes,
202            #[cfg(feature = "kubernetes")]
203            ClientConfig::KubernetesCloud { .. } => Platform::Kubernetes,
204            ClientConfig::Test => Platform::Test,
205            ClientConfig::Local { .. } => Platform::Local,
206            // This should never be reached when no features are enabled,
207            // as the enum would be uninhabitable
208            #[allow(unreachable_patterns)]
209            _ => unreachable!("ClientConfig requires at least one platform feature to be enabled"),
210        }
211    }
212
213    async fn impersonate(&self, config: ImpersonationConfig) -> Result<ClientConfig> {
214        match (self, config) {
215            #[cfg(feature = "aws")]
216            (ClientConfig::Aws(aws_config), ImpersonationConfig::Aws(imp_config)) => {
217                use alien_aws_clients::AwsClientConfigExt;
218                let new_config = aws_config.impersonate(imp_config).await.map_err(|e| {
219                    AlienError::new(ErrorData::AuthenticationError {
220                        message: format!("AWS role impersonation failed: {}", e),
221                    })
222                })?;
223                Ok(ClientConfig::Aws(Box::new(new_config)))
224            }
225            #[cfg(feature = "gcp")]
226            (ClientConfig::Gcp(gcp_config), ImpersonationConfig::Gcp(imp_config)) => {
227                use alien_gcp_clients::GcpClientConfigExt;
228                let new_config = gcp_config.impersonate(imp_config).await.map_err(|e| {
229                    AlienError::new(ErrorData::AuthenticationError {
230                        message: format!("GCP service account impersonation failed: {}", e),
231                    })
232                })?;
233                Ok(ClientConfig::Gcp(Box::new(new_config)))
234            }
235            #[cfg(feature = "azure")]
236            (ClientConfig::Azure(azure_config), ImpersonationConfig::Azure(imp_config)) => {
237                use alien_azure_clients::AzureClientConfigExt;
238                let new_config = azure_config.impersonate(imp_config).await.map_err(|e| {
239                    AlienError::new(ErrorData::AuthenticationError {
240                        message: format!("Azure managed identity impersonation failed: {}", e),
241                    })
242                })?;
243                Ok(ClientConfig::Azure(Box::new(new_config)))
244            }
245            // Kubernetes doesn't support impersonation
246            #[cfg(feature = "kubernetes")]
247            (ClientConfig::Kubernetes(_), _) => Err(AlienError::new(ErrorData::InvalidInput {
248                message: "Kubernetes platform does not support impersonation".to_string(),
249                field_name: Some("impersonation_config".to_string()),
250            })),
251            _ => Err(AlienError::new(ErrorData::InvalidInput {
252                message: "Platform config and impersonation config types must match".to_string(),
253                field_name: Some("impersonation_config".to_string()),
254            })),
255        }
256    }
257}