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    /// });
74    ///
75    /// let impersonated_config = aws_config.impersonate(impersonation).await?;
76    /// # Ok(())
77    /// # }
78    /// ```
79    async fn impersonate(&self, config: ImpersonationConfig) -> Result<ClientConfig>;
80}
81
82#[async_trait]
83impl ClientConfigExt for ClientConfig {
84    async fn from_env(
85        platform: Platform,
86        environment_variables: &HashMap<String, String>,
87    ) -> Result<Self> {
88        match platform {
89            #[cfg(feature = "aws")]
90            Platform::Aws => {
91                use alien_aws_clients::AwsClientConfigExt;
92                let config = alien_aws_clients::AwsClientConfig::from_env(environment_variables).await
93                    .map_err(|e| AlienError::new(ErrorData::InvalidClientConfig {
94                        message: format!("Failed to create AWS client configuration from environment variables: {}", e),
95                        errors: None,
96                    }))?;
97                Ok(ClientConfig::Aws(Box::new(config)))
98            }
99            #[cfg(not(feature = "aws"))]
100            Platform::Aws => Err(AlienError::new(ErrorData::InvalidClientConfig {
101                message: "AWS support is not enabled in this build".to_string(),
102                errors: None,
103            })),
104            #[cfg(feature = "gcp")]
105            Platform::Gcp => {
106                use alien_gcp_clients::GcpClientConfigExt;
107                let config = alien_gcp_clients::GcpClientConfig::from_env(environment_variables).await
108                    .map_err(|e| AlienError::new(ErrorData::InvalidClientConfig {
109                        message: format!("Failed to create GCP client configuration from environment variables: {}", e),
110                        errors: None,
111                    }))?;
112                Ok(ClientConfig::Gcp(Box::new(config)))
113            }
114            #[cfg(not(feature = "gcp"))]
115            Platform::Gcp => Err(AlienError::new(ErrorData::InvalidClientConfig {
116                message: "GCP support is not enabled in this build".to_string(),
117                errors: None,
118            })),
119            #[cfg(feature = "azure")]
120            Platform::Azure => {
121                use alien_azure_clients::AzureClientConfigExt;
122                let config = alien_azure_clients::AzureClientConfig::from_env(environment_variables).await
123                    .map_err(|e| AlienError::new(ErrorData::InvalidClientConfig {
124                        message: format!("Failed to create Azure client configuration from environment variables: {}", e),
125                        errors: None,
126                    }))?;
127                Ok(ClientConfig::Azure(Box::new(config)))
128            }
129            #[cfg(not(feature = "azure"))]
130            Platform::Azure => Err(AlienError::new(ErrorData::InvalidClientConfig {
131                message: "Azure support is not enabled in this build".to_string(),
132                errors: None,
133            })),
134            #[cfg(feature = "kubernetes")]
135            Platform::Kubernetes => {
136                use alien_k8s_clients::KubernetesClientConfigExt;
137                let config = alien_k8s_clients::KubernetesClientConfig::from_env(environment_variables).await
138                    .map_err(|e| AlienError::new(ErrorData::InvalidClientConfig {
139                        message: format!("Failed to create Kubernetes client configuration from environment variables: {}", e),
140                        errors: None,
141                    }))?;
142                Ok(ClientConfig::Kubernetes(Box::new(config)))
143            }
144            #[cfg(not(feature = "kubernetes"))]
145            Platform::Kubernetes => Err(AlienError::new(ErrorData::InvalidClientConfig {
146                message: "Kubernetes support is not enabled in this build".to_string(),
147                errors: None,
148            })),
149            Platform::Test => Ok(ClientConfig::Test),
150            Platform::Local => {
151                // Local platform reads state directory from ALIEN_LOCAL_STATE_DIRECTORY
152                let state_directory = environment_variables
153                    .get("ALIEN_LOCAL_STATE_DIRECTORY")
154                    .cloned()
155                    .unwrap_or_else(|| "/tmp/alien-local".to_string());
156
157                Ok(ClientConfig::Local {
158                    state_directory,
159                    artifact_registry_config: None,
160                })
161            }
162        }
163    }
164
165    async fn from_std_env(platform: Platform) -> Result<Self> {
166        let env_vars: HashMap<String, String> = std::env::vars().collect();
167        Self::from_env(platform, &env_vars).await
168    }
169
170    fn platform(&self) -> Platform {
171        match self {
172            #[cfg(feature = "aws")]
173            ClientConfig::Aws(_) => Platform::Aws,
174            #[cfg(feature = "gcp")]
175            ClientConfig::Gcp(_) => Platform::Gcp,
176            #[cfg(feature = "azure")]
177            ClientConfig::Azure(_) => Platform::Azure,
178            #[cfg(feature = "kubernetes")]
179            ClientConfig::Kubernetes(_) => Platform::Kubernetes,
180            ClientConfig::Test => Platform::Test,
181            ClientConfig::Local { .. } => Platform::Local,
182            // This should never be reached when no features are enabled,
183            // as the enum would be uninhabitable
184            #[allow(unreachable_patterns)]
185            _ => unreachable!("ClientConfig requires at least one platform feature to be enabled"),
186        }
187    }
188
189    async fn impersonate(&self, config: ImpersonationConfig) -> Result<ClientConfig> {
190        match (self, config) {
191            #[cfg(feature = "aws")]
192            (ClientConfig::Aws(aws_config), ImpersonationConfig::Aws(imp_config)) => {
193                use alien_aws_clients::AwsClientConfigExt;
194                let new_config = aws_config.impersonate(imp_config).await.map_err(|e| {
195                    AlienError::new(ErrorData::AuthenticationError {
196                        message: format!("AWS role impersonation failed: {}", e),
197                    })
198                })?;
199                Ok(ClientConfig::Aws(Box::new(new_config)))
200            }
201            #[cfg(feature = "gcp")]
202            (ClientConfig::Gcp(gcp_config), ImpersonationConfig::Gcp(imp_config)) => {
203                use alien_gcp_clients::GcpClientConfigExt;
204                let new_config = gcp_config.impersonate(imp_config).await.map_err(|e| {
205                    AlienError::new(ErrorData::AuthenticationError {
206                        message: format!("GCP service account impersonation failed: {}", e),
207                    })
208                })?;
209                Ok(ClientConfig::Gcp(Box::new(new_config)))
210            }
211            #[cfg(feature = "azure")]
212            (ClientConfig::Azure(azure_config), ImpersonationConfig::Azure(imp_config)) => {
213                use alien_azure_clients::AzureClientConfigExt;
214                let new_config = azure_config.impersonate(imp_config).await.map_err(|e| {
215                    AlienError::new(ErrorData::AuthenticationError {
216                        message: format!("Azure managed identity impersonation failed: {}", e),
217                    })
218                })?;
219                Ok(ClientConfig::Azure(Box::new(new_config)))
220            }
221            // Kubernetes doesn't support impersonation
222            #[cfg(feature = "kubernetes")]
223            (ClientConfig::Kubernetes(_), _) => Err(AlienError::new(ErrorData::InvalidInput {
224                message: "Kubernetes platform does not support impersonation".to_string(),
225                field_name: Some("impersonation_config".to_string()),
226            })),
227            _ => Err(AlienError::new(ErrorData::InvalidInput {
228                message: "Platform config and impersonation config types must match".to_string(),
229                field_name: Some("impersonation_config".to_string()),
230            })),
231        }
232    }
233}