Skip to main content

alien_bindings/
provider.rs

1//! Unified BindingsProvider implementation that supports multiple cloud providers
2
3use crate::{
4    error::{ErrorData, Result},
5    traits::{ArtifactRegistry, BindingsProviderApi, Build, Function, Kv, Queue, Storage, Vault},
6};
7
8use alien_client_config::ClientConfigExt;
9use alien_core::{ClientConfig, Platform, StackState};
10use alien_error::{AlienError, Context, IntoAlienError};
11use async_trait::async_trait;
12use serde::Deserialize;
13use std::{collections::HashMap, sync::Arc};
14
15/// Direct platform-specific bindings provider.
16/// Routes to appropriate platform implementations based on binding configuration.
17#[derive(Debug, Clone)]
18pub struct BindingsProvider {
19    client_config: ClientConfig,
20    bindings: HashMap<String, serde_json::Value>,
21}
22
23impl BindingsProvider {
24    /// Creates a new BindingsProvider with explicit credentials and bindings.
25    ///
26    /// This is the base constructor used by all other convenience constructors.
27    pub fn new(
28        client_config: ClientConfig,
29        bindings: HashMap<String, serde_json::Value>,
30    ) -> Result<Self> {
31        Ok(Self {
32            client_config,
33            bindings,
34        })
35    }
36
37    /// Creates a BindingsProvider from environment variables (for runtime use).
38    ///
39    /// This parses the platform from ALIEN_DEPLOYMENT_TYPE, loads ClientConfig from environment,
40    /// and extracts all ALIEN_*_BINDING environment variables.
41    pub async fn from_env(env: HashMap<String, String>) -> Result<Self> {
42        // 1. Parse platform from ALIEN_DEPLOYMENT_TYPE
43        let platform = crate::get_platform_from_env(&env)?;
44
45        // 2. Load ClientConfig from environment
46        let client_config = ClientConfig::from_env(platform, &env).await.map_err(|e| {
47            AlienError::new(ErrorData::ClientConfigInvalid {
48                platform,
49                message: format!("Failed to load client config: {}", e),
50            })
51        })?;
52
53        // 3. Parse all ALIEN_*_BINDING environment variables
54        let bindings = Self::parse_bindings_from_env(&env)?;
55
56        Self::new(client_config, bindings)
57    }
58
59    /// Parses all ALIEN_*_BINDING environment variables into a map.
60    fn parse_bindings_from_env(
61        env: &HashMap<String, String>,
62    ) -> Result<HashMap<String, serde_json::Value>> {
63        let mut bindings = HashMap::new();
64        for (key, value) in env {
65            if key.starts_with("ALIEN_") && key.ends_with("_BINDING") {
66                let binding_name = key
67                    .strip_prefix("ALIEN_")
68                    .unwrap()
69                    .strip_suffix("_BINDING")
70                    .unwrap()
71                    .to_lowercase()
72                    .replace('_', "-");
73                let parsed: serde_json::Value = serde_json::from_str(value)
74                    .into_alien_error()
75                    .context(ErrorData::BindingConfigInvalid {
76                        binding_name: binding_name.clone(),
77                        reason: "Failed to parse binding JSON".to_string(),
78                    })?;
79                bindings.insert(binding_name, parsed);
80            }
81        }
82        Ok(bindings)
83    }
84
85    /// Pattern 1: Creates a BindingsProvider from stack state and client config.
86    ///
87    /// Convenience helper that extracts bindings from stack state's remote_binding_params.
88    /// Only resources with `remote_access: true` will have binding params available.
89    ///
90    /// **When to use:** You already have `ClientConfig` and `StackState`.
91    ///
92    /// **Example use cases:**
93    /// - alien-deployment (has credentials from RemoteAccessResolver)
94    /// - Platform API backend (has stack state from DB)
95    pub fn from_stack_state(stack_state: &StackState, client_config: ClientConfig) -> Result<Self> {
96        let bindings = stack_state
97            .resources
98            .iter()
99            .filter_map(|(id, state)| {
100                state
101                    .remote_binding_params
102                    .as_ref()
103                    .map(|p| (id.clone(), p.clone()))
104            })
105            .collect();
106
107        Self::new(client_config, bindings)
108    }
109
110    /// Pattern 2: Creates a BindingsProvider for remote deployment access.
111    ///
112    /// Fetches credentials remotely using deployment ID and auth token, then creates the provider.
113    ///
114    /// **When to use:** You have a deployment ID and auth token, but no credentials yet.
115    ///
116    /// **Example use cases:**
117    /// - CLI commands (e.g., `alien secrets set`)
118    /// - External applications connecting to deployment
119    /// - Local development tools
120    ///
121    /// **What it does internally:**
122    /// 1. GET /api/deployments/{id} - Returns deployment info (stackState, platform, managerId)
123    /// 2. GET /api/managers/{managerId} - Returns manager URL
124    /// 3. POST {managerUrl}/v1/deployment/resolve-credentials - Resolves credentials
125    /// 4. Creates BindingsProvider using from_stack_state()
126    #[cfg(feature = "platform-sdk")]
127    pub async fn for_remote_deployment(
128        deployment_id: &str,
129        _token: &str,
130        api_base_url: Option<&str>,
131    ) -> Result<Self> {
132        let base_url = api_base_url.unwrap_or("https://api.alien.dev");
133
134        // Create SDK client
135        let sdk_client = alien_platform_api::Client::new(base_url);
136
137        // 1. Get deployment info
138        let deployment_response = sdk_client
139            .get_deployment()
140            .id(deployment_id)
141            .send()
142            .await
143            .into_alien_error()
144            .context(ErrorData::RemoteAccessFailed {
145                operation: "fetch deployment from Platform API".to_string(),
146            })?
147            .into_inner();
148
149        // 2. Get manager URL
150        let manager_id = deployment_response.manager_id.ok_or_else(|| {
151            AlienError::new(ErrorData::RemoteAccessFailed {
152                operation: "fetch manager from Platform API".to_string(),
153            })
154        })?;
155
156        let manager_response = sdk_client
157            .get_manager()
158            .id(&manager_id.to_string())
159            .send()
160            .await
161            .into_alien_error()
162            .context(ErrorData::RemoteAccessFailed {
163                operation: "fetch manager from Platform API".to_string(),
164            })?
165            .into_inner();
166
167        // 3. Convert SDK stack state to alien-core StackState
168        let stack_state = deployment_response.stack_state.as_ref().ok_or_else(|| {
169            AlienError::new(ErrorData::RemoteAccessFailed {
170                operation: "Deployment has no stack state (not deployed yet)".to_string(),
171            })
172        })?;
173
174        let alien_stack_state = conversions::convert_stack_state(stack_state)?;
175
176        // 4. Resolve client config from manager
177        let manager_url = manager_response.url.ok_or_else(|| {
178            AlienError::new(ErrorData::RemoteAccessFailed {
179                operation: "fetch manager URL from Platform API".to_string(),
180            })
181        })?;
182
183        let http_client = reqwest::Client::new();
184        let client_config = http_client
185            .post(format!("{}/v1/deployment/resolve-credentials", manager_url))
186            .json(&serde_json::json!({
187                "platform": deployment_response.platform,
188                "stackState": stack_state,
189            }))
190            .send()
191            .await
192            .into_alien_error()
193            .context(ErrorData::RemoteAccessFailed {
194                operation: "resolve credentials from manager".to_string(),
195            })?
196            .json::<ResolveCredentialsResponse>()
197            .await
198            .into_alien_error()
199            .context(ErrorData::RemoteAccessFailed {
200                operation: "parse credentials response".to_string(),
201            })?
202            .client_config;
203
204        // 5. Create provider using from_stack_state (which extracts bindings from stack_state)
205        Self::from_stack_state(&alien_stack_state, client_config)
206    }
207}
208
209#[cfg(feature = "platform-sdk")]
210#[derive(Deserialize)]
211#[serde(rename_all = "camelCase")]
212struct ResolveCredentialsResponse {
213    client_config: ClientConfig,
214}
215
216#[async_trait]
217impl BindingsProviderApi for BindingsProvider {
218    async fn load_storage(&self, binding_name: &str) -> Result<Arc<dyn Storage>> {
219        use alien_core::bindings::StorageBinding;
220
221        // Get binding JSON from our pre-parsed map
222        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
223            AlienError::new(ErrorData::BindingConfigInvalid {
224                binding_name: binding_name.to_string(),
225                reason: "Binding not found".to_string(),
226            })
227        })?;
228
229        // Parse to strongly-typed binding
230        let binding: StorageBinding = serde_json::from_value(binding_json.clone())
231            .into_alien_error()
232            .context(ErrorData::BindingConfigInvalid {
233                binding_name: binding_name.to_string(),
234                reason: "Failed to parse storage binding".to_string(),
235            })?;
236
237        match binding {
238            #[cfg(feature = "aws")]
239            StorageBinding::S3(config) => {
240                use crate::providers::storage::aws_s3::S3Storage;
241
242                // Get AWS config from our stored ClientConfig
243                let aws_config = self.client_config.aws_config().ok_or_else(|| {
244                    AlienError::new(ErrorData::ClientConfigInvalid {
245                        platform: Platform::Aws,
246                        message: "AWS config not available".to_string(),
247                    })
248                })?;
249
250                // Extract bucket name from binding
251                let bucket_name = config
252                    .bucket_name
253                    .into_value(binding_name, "bucket_name")
254                    .context(ErrorData::BindingConfigInvalid {
255                        binding_name: binding_name.to_string(),
256                        reason: "Failed to extract bucket_name from S3 binding".to_string(),
257                    })?;
258
259                let storage = Arc::new(S3Storage::new(bucket_name, aws_config)?);
260                Ok(storage)
261            }
262            #[cfg(not(feature = "aws"))]
263            StorageBinding::S3 { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
264                feature: "aws".to_string(),
265            })),
266
267            #[cfg(feature = "azure")]
268            StorageBinding::Blob(config) => {
269                use crate::providers::storage::azure_blob::BlobStorage;
270
271                let azure_config = self.client_config.azure_config().ok_or_else(|| {
272                    AlienError::new(ErrorData::ClientConfigInvalid {
273                        platform: Platform::Azure,
274                        message: "Azure config not available".to_string(),
275                    })
276                })?;
277
278                // Extract container and account names from binding
279                let container_name = config
280                    .container_name
281                    .into_value(binding_name, "container_name")
282                    .context(ErrorData::BindingConfigInvalid {
283                        binding_name: binding_name.to_string(),
284                        reason: "Failed to extract container_name from Blob binding".to_string(),
285                    })?;
286
287                let account_name = config
288                    .account_name
289                    .into_value(binding_name, "account_name")
290                    .context(ErrorData::BindingConfigInvalid {
291                        binding_name: binding_name.to_string(),
292                        reason: "Failed to extract account_name from Blob binding".to_string(),
293                    })?;
294
295                let storage = Arc::new(BlobStorage::new(
296                    container_name,
297                    account_name,
298                    azure_config,
299                )?);
300                Ok(storage)
301            }
302            #[cfg(not(feature = "azure"))]
303            StorageBinding::Blob { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
304                feature: "azure".to_string(),
305            })),
306
307            #[cfg(feature = "gcp")]
308            StorageBinding::Gcs(config) => {
309                use crate::providers::storage::gcp_gcs::GcsStorage;
310
311                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
312                    AlienError::new(ErrorData::ClientConfigInvalid {
313                        platform: Platform::Gcp,
314                        message: "GCP config not available".to_string(),
315                    })
316                })?;
317
318                // Extract bucket name from binding
319                let bucket_name = config
320                    .bucket_name
321                    .into_value(binding_name, "bucket_name")
322                    .context(ErrorData::BindingConfigInvalid {
323                        binding_name: binding_name.to_string(),
324                        reason: "Failed to extract bucket_name from Gcs binding".to_string(),
325                    })?;
326
327                let storage = Arc::new(GcsStorage::new(bucket_name, gcp_config)?);
328                Ok(storage)
329            }
330            #[cfg(not(feature = "gcp"))]
331            StorageBinding::Gcs { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
332                feature: "gcp".to_string(),
333            })),
334
335            #[cfg(feature = "local")]
336            StorageBinding::Local(config) => {
337                use crate::providers::storage::local::LocalStorage;
338
339                // Extract storage path from binding
340                let storage_path = config
341                    .storage_path
342                    .into_value(binding_name, "storage_path")
343                    .context(ErrorData::BindingConfigInvalid {
344                        binding_name: binding_name.to_string(),
345                        reason: "Failed to extract storage_path from Local binding".to_string(),
346                    })?;
347
348                let storage = Arc::new(LocalStorage::new(storage_path)?);
349                Ok(storage)
350            }
351            #[cfg(not(feature = "local"))]
352            StorageBinding::Local { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
353                feature: "local".to_string(),
354            })),
355        }
356    }
357
358    async fn load_build(&self, binding_name: &str) -> Result<Arc<dyn Build>> {
359        use alien_core::bindings::BuildBinding;
360
361        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
362            AlienError::new(ErrorData::BindingConfigInvalid {
363                binding_name: binding_name.to_string(),
364                reason: "Binding not found".to_string(),
365            })
366        })?;
367
368        let binding: BuildBinding = serde_json::from_value(binding_json.clone())
369            .into_alien_error()
370            .context(ErrorData::BindingConfigInvalid {
371                binding_name: binding_name.to_string(),
372                reason: "Failed to parse build binding".to_string(),
373            })?;
374
375        match binding {
376            #[cfg(feature = "aws")]
377            BuildBinding::Codebuild { .. } => {
378                use crate::providers::build::codebuild::CodebuildBuild;
379
380                let aws_config = self.client_config.aws_config().ok_or_else(|| {
381                    AlienError::new(ErrorData::ClientConfigInvalid {
382                        platform: Platform::Aws,
383                        message: "AWS config not available".to_string(),
384                    })
385                })?;
386
387                let build = Arc::new(
388                    CodebuildBuild::new(binding_name.to_string(), binding, aws_config)
389                        .await
390                        .context(ErrorData::BindingConfigInvalid {
391                            binding_name: binding_name.to_string(),
392                            reason: "Failed to initialize AWS CodeBuild client".to_string(),
393                        })?,
394                );
395                Ok(build)
396            }
397            #[cfg(not(feature = "aws"))]
398            BuildBinding::Codebuild { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
399                feature: "aws".to_string(),
400            })),
401
402            #[cfg(feature = "azure")]
403            BuildBinding::Aca { .. } => {
404                use crate::providers::build::aca::AcaBuild;
405
406                let azure_config = self.client_config.azure_config().ok_or_else(|| {
407                    AlienError::new(ErrorData::ClientConfigInvalid {
408                        platform: Platform::Azure,
409                        message: "Azure config not available".to_string(),
410                    })
411                })?;
412
413                let build = Arc::new(
414                    AcaBuild::new(binding_name.to_string(), binding, azure_config)
415                        .await
416                        .context(ErrorData::BindingConfigInvalid {
417                            binding_name: binding_name.to_string(),
418                            reason: "Failed to initialize Azure Container Apps build".to_string(),
419                        })?,
420                );
421                Ok(build)
422            }
423            #[cfg(not(feature = "azure"))]
424            BuildBinding::Aca { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
425                feature: "azure".to_string(),
426            })),
427
428            #[cfg(feature = "gcp")]
429            BuildBinding::Cloudbuild { .. } => {
430                use crate::providers::build::cloudbuild::CloudbuildBuild;
431
432                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
433                    AlienError::new(ErrorData::ClientConfigInvalid {
434                        platform: Platform::Gcp,
435                        message: "GCP config not available".to_string(),
436                    })
437                })?;
438
439                let build = Arc::new(
440                    CloudbuildBuild::new(binding_name.to_string(), binding, gcp_config)
441                        .await
442                        .context(ErrorData::BindingConfigInvalid {
443                            binding_name: binding_name.to_string(),
444                            reason: "Failed to initialize GCP Cloud Build client".to_string(),
445                        })?,
446                );
447                Ok(build)
448            }
449            #[cfg(not(feature = "gcp"))]
450            BuildBinding::Cloudbuild { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
451                feature: "gcp".to_string(),
452            })),
453
454            #[cfg(feature = "local")]
455            BuildBinding::Local { .. } => {
456                use crate::providers::build::local::LocalBuild;
457
458                let build = Arc::new(LocalBuild::new(binding_name.to_string(), binding)?);
459                Ok(build)
460            }
461            #[cfg(not(feature = "local"))]
462            BuildBinding::Local { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
463                feature: "local".to_string(),
464            })),
465
466            #[cfg(feature = "kubernetes")]
467            BuildBinding::Kubernetes { .. } => {
468                use crate::providers::build::kubernetes::KubernetesBuild;
469
470                let build =
471                    Arc::new(KubernetesBuild::new(binding_name.to_string(), binding).await?);
472                Ok(build)
473            }
474            #[cfg(not(feature = "kubernetes"))]
475            BuildBinding::Kubernetes { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
476                feature: "kubernetes".to_string(),
477            })),
478        }
479    }
480
481    async fn load_artifact_registry(
482        &self,
483        binding_name: &str,
484    ) -> Result<Arc<dyn ArtifactRegistry>> {
485        use alien_core::bindings::ArtifactRegistryBinding;
486
487        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
488            AlienError::new(ErrorData::BindingConfigInvalid {
489                binding_name: binding_name.to_string(),
490                reason: "Binding not found".to_string(),
491            })
492        })?;
493
494        let binding: ArtifactRegistryBinding = serde_json::from_value(binding_json.clone())
495            .into_alien_error()
496            .context(ErrorData::BindingConfigInvalid {
497                binding_name: binding_name.to_string(),
498                reason: "Failed to parse artifact registry binding".to_string(),
499            })?;
500
501        match binding {
502            #[cfg(feature = "aws")]
503            ArtifactRegistryBinding::Ecr { .. } => {
504                use crate::providers::artifact_registry::ecr::EcrArtifactRegistry;
505
506                let aws_config = self.client_config.aws_config().ok_or_else(|| {
507                    AlienError::new(ErrorData::ClientConfigInvalid {
508                        platform: Platform::Aws,
509                        message: "AWS config not available".to_string(),
510                    })
511                })?;
512
513                let registry = Arc::new(
514                    EcrArtifactRegistry::new(binding_name.to_string(), binding, aws_config)
515                        .await
516                        .context(ErrorData::BindingConfigInvalid {
517                            binding_name: binding_name.to_string(),
518                            reason: "Failed to initialize AWS ECR artifact registry".to_string(),
519                        })?,
520                );
521                Ok(registry)
522            }
523            #[cfg(not(feature = "aws"))]
524            ArtifactRegistryBinding::Ecr { .. } => {
525                Err(AlienError::new(ErrorData::FeatureNotEnabled {
526                    feature: "aws".to_string(),
527                }))
528            }
529
530            #[cfg(feature = "azure")]
531            ArtifactRegistryBinding::Acr { .. } => {
532                use crate::providers::artifact_registry::acr::AcrArtifactRegistry;
533
534                let azure_config = self.client_config.azure_config().ok_or_else(|| {
535                    AlienError::new(ErrorData::ClientConfigInvalid {
536                        platform: Platform::Azure,
537                        message: "Azure config not available".to_string(),
538                    })
539                })?;
540
541                let registry = Arc::new(
542                    AcrArtifactRegistry::new(binding_name.to_string(), binding, azure_config)
543                        .await
544                        .context(ErrorData::BindingConfigInvalid {
545                            binding_name: binding_name.to_string(),
546                            reason: "Failed to initialize Azure ACR artifact registry".to_string(),
547                        })?,
548                );
549                Ok(registry)
550            }
551            #[cfg(not(feature = "azure"))]
552            ArtifactRegistryBinding::Acr { .. } => {
553                Err(AlienError::new(ErrorData::FeatureNotEnabled {
554                    feature: "azure".to_string(),
555                }))
556            }
557
558            #[cfg(feature = "gcp")]
559            ArtifactRegistryBinding::Gar { .. } => {
560                use crate::providers::artifact_registry::gar::GarArtifactRegistry;
561
562                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
563                    AlienError::new(ErrorData::ClientConfigInvalid {
564                        platform: Platform::Gcp,
565                        message: "GCP config not available".to_string(),
566                    })
567                })?;
568
569                let registry = Arc::new(
570                    GarArtifactRegistry::new(binding_name.to_string(), binding, gcp_config)
571                        .await
572                        .context(ErrorData::BindingConfigInvalid {
573                            binding_name: binding_name.to_string(),
574                            reason: "Failed to initialize GCP GAR artifact registry".to_string(),
575                        })?,
576                );
577                Ok(registry)
578            }
579            #[cfg(not(feature = "gcp"))]
580            ArtifactRegistryBinding::Gar { .. } => {
581                Err(AlienError::new(ErrorData::FeatureNotEnabled {
582                    feature: "gcp".to_string(),
583                }))
584            }
585
586            #[cfg(feature = "local")]
587            ArtifactRegistryBinding::Local { .. } => {
588                use crate::providers::artifact_registry::local::LocalArtifactRegistry;
589
590                let registry = Arc::new(
591                    LocalArtifactRegistry::new(binding_name.to_string(), binding.clone()).await?,
592                );
593                Ok(registry)
594            }
595            #[cfg(not(feature = "local"))]
596            ArtifactRegistryBinding::Local { .. } => {
597                Err(AlienError::new(ErrorData::FeatureNotEnabled {
598                    feature: "local".to_string(),
599                }))
600            }
601        }
602    }
603
604    async fn load_vault(&self, binding_name: &str) -> Result<Arc<dyn Vault>> {
605        use alien_core::bindings::VaultBinding;
606
607        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
608            AlienError::new(ErrorData::BindingConfigInvalid {
609                binding_name: binding_name.to_string(),
610                reason: "Binding not found".to_string(),
611            })
612        })?;
613
614        let binding: VaultBinding = serde_json::from_value(binding_json.clone())
615            .into_alien_error()
616            .context(ErrorData::BindingConfigInvalid {
617                binding_name: binding_name.to_string(),
618                reason: "Failed to parse vault binding".to_string(),
619            })?;
620
621        match binding {
622            #[cfg(feature = "aws")]
623            VaultBinding::ParameterStore(config) => {
624                use crate::providers::vault::aws_parameter_store::AwsParameterStoreVault;
625                use alien_aws_clients::ssm::SsmClient;
626
627                let aws_config = self.client_config.aws_config().ok_or_else(|| {
628                    AlienError::new(ErrorData::ClientConfigInvalid {
629                        platform: Platform::Aws,
630                        message: "AWS config not available".to_string(),
631                    })
632                })?;
633
634                let client = Arc::new(SsmClient::new(
635                    crate::http_client::create_http_client(),
636                    aws_config.clone(),
637                ));
638
639                // Extract the vault prefix from the binding configuration
640                let vault_prefix = config
641                    .vault_prefix
642                    .into_value(&binding_name, "vault_prefix")
643                    .context(ErrorData::BindingConfigInvalid {
644                        binding_name: binding_name.to_string(),
645                        reason: "Failed to extract vault_prefix from ParameterStore binding"
646                            .to_string(),
647                    })?;
648
649                let vault = Arc::new(AwsParameterStoreVault::new(client, vault_prefix));
650                Ok(vault)
651            }
652            #[cfg(not(feature = "aws"))]
653            VaultBinding::ParameterStore(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
654                feature: "aws".to_string(),
655            })),
656
657            #[cfg(feature = "azure")]
658            VaultBinding::KeyVault(config) => {
659                use crate::providers::vault::azure_key_vault::AzureKeyVault;
660                use alien_azure_clients::keyvault::AzureKeyVaultSecretsClient;
661
662                let azure_config = self.client_config.azure_config().ok_or_else(|| {
663                    AlienError::new(ErrorData::ClientConfigInvalid {
664                        platform: Platform::Azure,
665                        message: "Azure config not available".to_string(),
666                    })
667                })?;
668
669                let client = Arc::new(AzureKeyVaultSecretsClient::new(
670                    crate::http_client::create_http_client(),
671                    azure_config.clone(),
672                ));
673
674                // Extract the vault name from the binding configuration
675                let vault_name = config
676                    .vault_name
677                    .into_value(&binding_name, "vault_name")
678                    .context(ErrorData::BindingConfigInvalid {
679                        binding_name: binding_name.to_string(),
680                        reason: "Failed to extract vault_name from KeyVault binding".to_string(),
681                    })?;
682
683                // Construct the vault base URL
684                // Azure Key Vault URLs typically follow: https://{vault-name}.vault.azure.net/
685                let vault_base_url = format!("https://{}.vault.azure.net", vault_name);
686
687                let vault = Arc::new(AzureKeyVault::new(client, vault_base_url));
688                Ok(vault)
689            }
690            #[cfg(not(feature = "azure"))]
691            VaultBinding::KeyVault(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
692                feature: "azure".to_string(),
693            })),
694
695            #[cfg(feature = "gcp")]
696            VaultBinding::SecretManager(config) => {
697                use crate::providers::vault::gcp_secret_manager::GcpSecretManagerVault;
698                use alien_gcp_clients::secret_manager::SecretManagerClient;
699
700                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
701                    AlienError::new(ErrorData::ClientConfigInvalid {
702                        platform: Platform::Gcp,
703                        message: "GCP config not available".to_string(),
704                    })
705                })?;
706
707                let client = Arc::new(SecretManagerClient::new(
708                    crate::http_client::create_http_client(),
709                    gcp_config.clone(),
710                ));
711
712                // Extract the vault prefix from the binding configuration
713                let vault_prefix = config
714                    .vault_prefix
715                    .into_value(&binding_name, "vault_prefix")
716                    .context(ErrorData::BindingConfigInvalid {
717                        binding_name: binding_name.to_string(),
718                        reason: "Failed to extract vault_prefix from SecretManager binding"
719                            .to_string(),
720                    })?;
721
722                let vault = Arc::new(GcpSecretManagerVault::new(
723                    client,
724                    vault_prefix,
725                    gcp_config.project_id.clone(),
726                ));
727                Ok(vault)
728            }
729            #[cfg(not(feature = "gcp"))]
730            VaultBinding::SecretManager(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
731                feature: "gcp".to_string(),
732            })),
733
734            #[cfg(feature = "local")]
735            VaultBinding::Local(config) => {
736                use crate::providers::vault::local::LocalVault;
737
738                let vault_dir = config
739                    .data_dir
740                    .into_value(binding_name, "data_dir")
741                    .context(ErrorData::BindingConfigInvalid {
742                        binding_name: binding_name.to_string(),
743                        reason: "Failed to extract data_dir from vault binding".to_string(),
744                    })?;
745
746                let vault = Arc::new(LocalVault::new(
747                    binding_name.to_string(),
748                    std::path::PathBuf::from(vault_dir),
749                ));
750                Ok(vault)
751            }
752            #[cfg(not(feature = "local"))]
753            VaultBinding::Local { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
754                feature: "local".to_string(),
755            })),
756
757            #[cfg(feature = "kubernetes")]
758            VaultBinding::KubernetesSecret(config) => {
759                use crate::providers::vault::kubernetes_secret::KubernetesSecretVault;
760                use alien_k8s_clients::{secrets::SecretsApi, KubernetesClient};
761
762                let kubernetes_config =
763                    self.client_config.kubernetes_config().ok_or_else(|| {
764                        AlienError::new(ErrorData::ClientConfigInvalid {
765                            platform: Platform::Kubernetes,
766                            message: "Kubernetes config not available".to_string(),
767                        })
768                    })?;
769
770                let kubernetes_client = KubernetesClient::new(kubernetes_config.clone())
771                    .await
772                    .context(ErrorData::CloudPlatformError {
773                        message: "Failed to create Kubernetes client for vault".to_string(),
774                        resource_id: None,
775                    })?;
776
777                let client: Arc<dyn SecretsApi> = Arc::new(kubernetes_client);
778
779                // Extract namespace and vault prefix from binding
780                let namespace = config
781                    .namespace
782                    .into_value(binding_name, "namespace")
783                    .context(ErrorData::BindingConfigInvalid {
784                        binding_name: binding_name.to_string(),
785                        reason: "Failed to extract namespace from KubernetesSecret binding"
786                            .to_string(),
787                    })?;
788
789                let vault_prefix = config
790                    .vault_prefix
791                    .into_value(binding_name, "vault_prefix")
792                    .context(ErrorData::BindingConfigInvalid {
793                        binding_name: binding_name.to_string(),
794                        reason: "Failed to extract vault_prefix from KubernetesSecret binding"
795                            .to_string(),
796                    })?;
797
798                let vault = Arc::new(KubernetesSecretVault::new(client, namespace, vault_prefix));
799                Ok(vault)
800            }
801            #[cfg(not(feature = "kubernetes"))]
802            VaultBinding::KubernetesSecret(_) => {
803                Err(AlienError::new(ErrorData::FeatureNotEnabled {
804                    feature: "kubernetes".to_string(),
805                }))
806            }
807        }
808    }
809
810    async fn load_kv(&self, binding_name: &str) -> Result<Arc<dyn Kv>> {
811        use alien_core::bindings::KvBinding;
812
813        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
814            AlienError::new(ErrorData::BindingConfigInvalid {
815                binding_name: binding_name.to_string(),
816                reason: "Binding not found".to_string(),
817            })
818        })?;
819
820        let binding: KvBinding = serde_json::from_value(binding_json.clone())
821            .into_alien_error()
822            .context(ErrorData::BindingConfigInvalid {
823                binding_name: binding_name.to_string(),
824                reason: "Failed to parse KV binding".to_string(),
825            })?;
826
827        match binding {
828            #[cfg(feature = "aws")]
829            KvBinding::Dynamodb(config) => {
830                use crate::providers::kv::aws_dynamodb::AwsDynamodbKv;
831
832                let table_name = config
833                    .table_name
834                    .into_value(binding_name, "table_name")
835                    .context(ErrorData::BindingConfigInvalid {
836                        binding_name: binding_name.to_string(),
837                        reason: "Failed to extract table_name from DynamoDB binding".to_string(),
838                    })?;
839
840                let aws_config = self.client_config.aws_config().ok_or_else(|| {
841                    AlienError::new(ErrorData::ClientConfigInvalid {
842                        platform: Platform::Aws,
843                        message: "AWS config not available".to_string(),
844                    })
845                })?;
846
847                let kv_impl = AwsDynamodbKv::new(table_name, aws_config.clone()).await?;
848                let kv: Arc<dyn Kv> = Arc::new(kv_impl);
849                Ok(kv)
850            }
851            #[cfg(not(feature = "aws"))]
852            KvBinding::Dynamodb(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
853                feature: "aws".to_string(),
854            })),
855
856            #[cfg(feature = "gcp")]
857            KvBinding::Firestore(config) => {
858                use crate::providers::kv::gcp_firestore::GcpFirestoreKv;
859                use alien_gcp_clients::firestore::FirestoreClient;
860
861                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
862                    AlienError::new(ErrorData::ClientConfigInvalid {
863                        platform: Platform::Gcp,
864                        message: "GCP config not available".to_string(),
865                    })
866                })?;
867
868                let client = FirestoreClient::new(
869                    crate::http_client::create_http_client(),
870                    gcp_config.clone(),
871                );
872
873                let project_id = config
874                    .project_id
875                    .into_value(binding_name, "project_id")
876                    .context(ErrorData::BindingConfigInvalid {
877                        binding_name: binding_name.to_string(),
878                        reason: "Failed to extract project_id from Firestore binding".to_string(),
879                    })?;
880
881                let database_id = config
882                    .database_id
883                    .into_value(binding_name, "database_id")
884                    .context(ErrorData::BindingConfigInvalid {
885                        binding_name: binding_name.to_string(),
886                        reason: "Failed to extract database_id from Firestore binding".to_string(),
887                    })?;
888
889                let collection_name = config
890                    .collection_name
891                    .into_value(binding_name, "collection_name")
892                    .context(ErrorData::BindingConfigInvalid {
893                        binding_name: binding_name.to_string(),
894                        reason: "Failed to extract collection_name from Firestore binding"
895                            .to_string(),
896                    })?;
897
898                let kv = Arc::new(GcpFirestoreKv::new(
899                    client,
900                    project_id,
901                    database_id,
902                    collection_name,
903                )?);
904                Ok(kv)
905            }
906            #[cfg(not(feature = "gcp"))]
907            KvBinding::Firestore(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
908                feature: "gcp".to_string(),
909            })),
910
911            #[cfg(feature = "azure")]
912            KvBinding::TableStorage(config) => {
913                use crate::providers::kv::azure_table_storage::AzureTableStorageKv;
914                use alien_azure_clients::storage_accounts::{
915                    AzureStorageAccountsClient, StorageAccountsApi,
916                };
917                use alien_azure_clients::tables::AzureTableStorageClient;
918
919                let azure_config = self.client_config.azure_config().ok_or_else(|| {
920                    AlienError::new(ErrorData::ClientConfigInvalid {
921                        platform: Platform::Azure,
922                        message: "Azure config not available".to_string(),
923                    })
924                })?;
925
926                let resource_group_name = config
927                    .resource_group_name
928                    .into_value(binding_name, "resource_group_name")
929                    .context(ErrorData::BindingConfigInvalid {
930                        binding_name: binding_name.to_string(),
931                        reason: "Failed to extract resource_group_name from TableStorage binding"
932                            .to_string(),
933                    })?;
934
935                let account_name = config
936                    .account_name
937                    .into_value(binding_name, "account_name")
938                    .context(ErrorData::BindingConfigInvalid {
939                        binding_name: binding_name.to_string(),
940                        reason: "Failed to extract account_name from TableStorage binding"
941                            .to_string(),
942                    })?;
943
944                let table_name = config
945                    .table_name
946                    .into_value(binding_name, "table_name")
947                    .context(ErrorData::BindingConfigInvalid {
948                        binding_name: binding_name.to_string(),
949                        reason: "Failed to extract table_name from TableStorage binding"
950                            .to_string(),
951                    })?;
952
953                // Fetch storage account key once during initialization
954                let storage_accounts_client = AzureStorageAccountsClient::new(
955                    crate::http_client::create_http_client(),
956                    azure_config.clone(),
957                );
958
959                let keys_result = storage_accounts_client
960                    .list_storage_account_keys(&resource_group_name, &account_name)
961                    .await
962                    .context(ErrorData::BindingConfigInvalid {
963                        binding_name: binding_name.to_string(),
964                        reason: "Failed to fetch storage account keys".to_string(),
965                    })?;
966
967                let storage_account_key = keys_result
968                    .keys
969                    .into_iter()
970                    .find(|key| key.key_name.as_deref() == Some("key1"))
971                    .and_then(|key| key.value)
972                    .ok_or_else(|| {
973                        AlienError::new(ErrorData::BindingConfigInvalid {
974                            binding_name: binding_name.to_string(),
975                            reason: format!(
976                                "No access key found for storage account '{}'",
977                                account_name
978                            ),
979                        })
980                    })?;
981
982                let client = AzureTableStorageClient::new(
983                    crate::http_client::create_http_client(),
984                    azure_config.clone(),
985                    storage_account_key,
986                );
987
988                let kv_impl =
989                    AzureTableStorageKv::new(client, resource_group_name, account_name, table_name);
990                let kv: Arc<dyn Kv> = Arc::new(kv_impl);
991                Ok(kv)
992            }
993            #[cfg(not(feature = "azure"))]
994            KvBinding::TableStorage(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
995                feature: "azure".to_string(),
996            })),
997
998            #[cfg(feature = "local")]
999            KvBinding::Local(local_binding) => {
1000                use crate::providers::kv::local::LocalKv;
1001                use std::path::PathBuf;
1002
1003                // Get data directory from binding
1004                let data_dir = PathBuf::from(
1005                    local_binding
1006                        .data_dir
1007                        .into_value(binding_name, "data_dir")
1008                        .context(ErrorData::BindingConfigInvalid {
1009                            binding_name: binding_name.to_string(),
1010                            reason: "Failed to extract data_dir from Local binding".to_string(),
1011                        })?,
1012                );
1013
1014                // Create local disk-persisted KV implementation
1015                let kv_impl = LocalKv::new(data_dir).await?;
1016
1017                let kv: Arc<dyn Kv> = Arc::new(kv_impl);
1018                Ok(kv)
1019            }
1020            #[cfg(not(feature = "local"))]
1021            KvBinding::Local { .. } => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1022                feature: "local".to_string(),
1023            })),
1024
1025            KvBinding::Redis(_) => Err(AlienError::new(ErrorData::NotImplemented {
1026                operation: "Redis KV binding".to_string(),
1027                reason: "Redis KV provider is not yet implemented".to_string(),
1028            })),
1029        }
1030    }
1031
1032    async fn load_queue(&self, binding_name: &str) -> Result<Arc<dyn Queue>> {
1033        use alien_core::bindings::QueueBinding;
1034
1035        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
1036            AlienError::new(ErrorData::BindingConfigInvalid {
1037                binding_name: binding_name.to_string(),
1038                reason: "Binding not found".to_string(),
1039            })
1040        })?;
1041
1042        let binding: QueueBinding = serde_json::from_value(binding_json.clone())
1043            .into_alien_error()
1044            .context(ErrorData::BindingConfigInvalid {
1045                binding_name: binding_name.to_string(),
1046                reason: "Failed to parse Queue binding".to_string(),
1047            })?;
1048
1049        match binding {
1050            #[cfg(feature = "aws")]
1051            QueueBinding::Sqs(config) => {
1052                use crate::providers::queue::aws_sqs::AwsSqsQueue;
1053
1054                let queue_url = config
1055                    .queue_url
1056                    .into_value(binding_name, "queue_url")
1057                    .context(ErrorData::BindingConfigInvalid {
1058                        binding_name: binding_name.to_string(),
1059                        reason: "Failed to extract queue_url from SQS binding".to_string(),
1060                    })?;
1061
1062                let aws_config = self.client_config.aws_config().ok_or_else(|| {
1063                    AlienError::new(ErrorData::ClientConfigInvalid {
1064                        platform: Platform::Aws,
1065                        message: "AWS config not available".to_string(),
1066                    })
1067                })?;
1068                let q: Arc<dyn Queue> =
1069                    Arc::new(AwsSqsQueue::new(queue_url, aws_config.clone()).await?);
1070                Ok(q)
1071            }
1072            #[cfg(not(feature = "aws"))]
1073            QueueBinding::Sqs(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1074                feature: "aws".to_string(),
1075            })),
1076
1077            #[cfg(feature = "gcp")]
1078            QueueBinding::Pubsub(config) => {
1079                use crate::providers::queue::gcp_pubsub::GcpPubSubQueue;
1080                let topic_name = config.topic.into_value(binding_name, "topic").context(
1081                    ErrorData::BindingConfigInvalid {
1082                        binding_name: binding_name.to_string(),
1083                        reason: "Failed to extract topic".to_string(),
1084                    },
1085                )?;
1086                let subscription_name = config
1087                    .subscription
1088                    .into_value(binding_name, "subscription")
1089                    .context(ErrorData::BindingConfigInvalid {
1090                        binding_name: binding_name.to_string(),
1091                        reason: "Failed to extract subscription".to_string(),
1092                    })?;
1093                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
1094                    AlienError::new(ErrorData::ClientConfigInvalid {
1095                        platform: Platform::Gcp,
1096                        message: "GCP config not available".to_string(),
1097                    })
1098                })?;
1099
1100                // Construct full resource names using the project ID from config
1101                let topic = if topic_name.starts_with("projects/") {
1102                    topic_name // Already a full resource name
1103                } else {
1104                    format!("projects/{}/topics/{}", gcp_config.project_id, topic_name)
1105                };
1106                let subscription = if subscription_name.starts_with("projects/") {
1107                    subscription_name // Already a full resource name
1108                } else {
1109                    format!(
1110                        "projects/{}/subscriptions/{}",
1111                        gcp_config.project_id, subscription_name
1112                    )
1113                };
1114
1115                let q: Arc<dyn Queue> =
1116                    Arc::new(GcpPubSubQueue::new(topic, subscription, gcp_config.clone()).await?);
1117                Ok(q)
1118            }
1119            #[cfg(not(feature = "gcp"))]
1120            QueueBinding::Pubsub(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1121                feature: "gcp".to_string(),
1122            })),
1123
1124            #[cfg(feature = "azure")]
1125            QueueBinding::Servicebus(config) => {
1126                use crate::providers::queue::azure_service_bus::AzureServiceBusQueue;
1127                let namespace = config
1128                    .namespace
1129                    .into_value(binding_name, "namespace")
1130                    .context(ErrorData::BindingConfigInvalid {
1131                        binding_name: binding_name.to_string(),
1132                        reason: "Failed to extract namespace".to_string(),
1133                    })?;
1134                let queue_name = config
1135                    .queue_name
1136                    .into_value(binding_name, "queue_name")
1137                    .context(ErrorData::BindingConfigInvalid {
1138                        binding_name: binding_name.to_string(),
1139                        reason: "Failed to extract queue_name".to_string(),
1140                    })?;
1141                let azure_config = self.client_config.azure_config().ok_or_else(|| {
1142                    AlienError::new(ErrorData::ClientConfigInvalid {
1143                        platform: Platform::Azure,
1144                        message: "Azure config not available".to_string(),
1145                    })
1146                })?;
1147                let q: Arc<dyn Queue> = Arc::new(
1148                    AzureServiceBusQueue::new(namespace, queue_name, azure_config.clone()).await?,
1149                );
1150                Ok(q)
1151            }
1152            #[cfg(not(feature = "azure"))]
1153            QueueBinding::Servicebus(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1154                feature: "azure".to_string(),
1155            })),
1156        }
1157    }
1158
1159    async fn load_function(&self, binding_name: &str) -> Result<Arc<dyn Function>> {
1160        use alien_core::bindings::FunctionBinding;
1161
1162        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
1163            AlienError::new(ErrorData::BindingConfigInvalid {
1164                binding_name: binding_name.to_string(),
1165                reason: "Binding not found".to_string(),
1166            })
1167        })?;
1168
1169        let binding: FunctionBinding = serde_json::from_value(binding_json.clone())
1170            .into_alien_error()
1171            .context(ErrorData::BindingConfigInvalid {
1172                binding_name: binding_name.to_string(),
1173                reason: "Failed to parse function binding".to_string(),
1174            })?;
1175
1176        match binding {
1177            #[cfg(feature = "aws")]
1178            FunctionBinding::Lambda(lambda_binding) => {
1179                use crate::providers::function::LambdaFunction;
1180
1181                let aws_config = self.client_config.aws_config().ok_or_else(|| {
1182                    AlienError::new(ErrorData::ClientConfigInvalid {
1183                        platform: Platform::Aws,
1184                        message: "AWS config not available".to_string(),
1185                    })
1186                })?;
1187                let client = crate::http_client::create_http_client();
1188
1189                let function_impl = LambdaFunction::new(client, aws_config.clone(), lambda_binding);
1190                let function: Arc<dyn Function> = Arc::new(function_impl);
1191                Ok(function)
1192            }
1193            #[cfg(not(feature = "aws"))]
1194            FunctionBinding::Lambda(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1195                feature: "aws".to_string(),
1196            })),
1197
1198            #[cfg(feature = "gcp")]
1199            FunctionBinding::CloudRun(cloudrun_binding) => {
1200                use crate::providers::function::CloudRunFunction;
1201
1202                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
1203                    AlienError::new(ErrorData::ClientConfigInvalid {
1204                        platform: Platform::Gcp,
1205                        message: "GCP config not available".to_string(),
1206                    })
1207                })?;
1208                let client = crate::http_client::create_http_client();
1209
1210                let function_impl =
1211                    CloudRunFunction::new(client, gcp_config.clone(), cloudrun_binding);
1212                let function: Arc<dyn Function> = Arc::new(function_impl);
1213                Ok(function)
1214            }
1215            #[cfg(not(feature = "gcp"))]
1216            FunctionBinding::CloudRun(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1217                feature: "gcp".to_string(),
1218            })),
1219
1220            #[cfg(feature = "azure")]
1221            FunctionBinding::ContainerApp(container_app_binding) => {
1222                use crate::providers::function::ContainerAppFunction;
1223
1224                let azure_config = self.client_config.azure_config().ok_or_else(|| {
1225                    AlienError::new(ErrorData::ClientConfigInvalid {
1226                        platform: Platform::Azure,
1227                        message: "Azure config not available".to_string(),
1228                    })
1229                })?;
1230                let client = crate::http_client::create_http_client();
1231
1232                let function_impl =
1233                    ContainerAppFunction::new(client, azure_config.clone(), container_app_binding);
1234                let function: Arc<dyn Function> = Arc::new(function_impl);
1235                Ok(function)
1236            }
1237            #[cfg(not(feature = "azure"))]
1238            FunctionBinding::ContainerApp(_) => {
1239                Err(AlienError::new(ErrorData::FeatureNotEnabled {
1240                    feature: "azure".to_string(),
1241                }))
1242            }
1243
1244            #[cfg(feature = "local")]
1245            FunctionBinding::Local(local_binding) => {
1246                use crate::providers::function::LocalFunction;
1247
1248                let function_impl = LocalFunction::new(local_binding);
1249                let function: Arc<dyn Function> = Arc::new(function_impl);
1250                Ok(function)
1251            }
1252            #[cfg(not(feature = "local"))]
1253            FunctionBinding::Local(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1254                feature: "local".to_string(),
1255            })),
1256
1257            #[cfg(feature = "kubernetes")]
1258            FunctionBinding::Kubernetes(kubernetes_binding) => {
1259                use crate::providers::function::KubernetesFunction;
1260
1261                let function_impl =
1262                    KubernetesFunction::new(binding_name.to_string(), kubernetes_binding)?;
1263                let function: Arc<dyn Function> = Arc::new(function_impl);
1264                Ok(function)
1265            }
1266            #[cfg(not(feature = "kubernetes"))]
1267            FunctionBinding::Kubernetes(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1268                feature: "kubernetes".to_string(),
1269            })),
1270        }
1271    }
1272
1273    async fn load_container(
1274        &self,
1275        binding_name: &str,
1276    ) -> Result<Arc<dyn crate::traits::Container>> {
1277        use alien_core::bindings::ContainerBinding;
1278
1279        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
1280            AlienError::new(ErrorData::BindingConfigInvalid {
1281                binding_name: binding_name.to_string(),
1282                reason: "Binding not found".to_string(),
1283            })
1284        })?;
1285
1286        let binding: ContainerBinding = serde_json::from_value(binding_json.clone())
1287            .into_alien_error()
1288            .context(ErrorData::BindingConfigInvalid {
1289                binding_name: binding_name.to_string(),
1290                reason: "Failed to parse container binding".to_string(),
1291            })?;
1292
1293        match binding {
1294            ContainerBinding::Horizon(horizon_binding) => {
1295                use crate::providers::container::HorizonContainer;
1296
1297                let container_impl = HorizonContainer::new(horizon_binding)?;
1298                let container: Arc<dyn crate::traits::Container> = Arc::new(container_impl);
1299                Ok(container)
1300            }
1301
1302            #[cfg(feature = "local")]
1303            ContainerBinding::Local(local_binding) => {
1304                use crate::providers::container::LocalContainer;
1305
1306                let container_impl = LocalContainer::new(local_binding)?;
1307                let container: Arc<dyn crate::traits::Container> = Arc::new(container_impl);
1308                Ok(container)
1309            }
1310            #[cfg(not(feature = "local"))]
1311            ContainerBinding::Local(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1312                feature: "local".to_string(),
1313            })),
1314
1315            #[cfg(feature = "kubernetes")]
1316            ContainerBinding::Kubernetes(kubernetes_binding) => {
1317                use crate::providers::container::KubernetesContainer;
1318
1319                let container_impl =
1320                    KubernetesContainer::new(binding_name.to_string(), kubernetes_binding)?;
1321                let container: Arc<dyn crate::traits::Container> = Arc::new(container_impl);
1322                Ok(container)
1323            }
1324            #[cfg(not(feature = "kubernetes"))]
1325            ContainerBinding::Kubernetes(_) => Err(AlienError::new(ErrorData::FeatureNotEnabled {
1326                feature: "kubernetes".to_string(),
1327            })),
1328        }
1329    }
1330
1331    async fn load_service_account(
1332        &self,
1333        binding_name: &str,
1334    ) -> Result<Arc<dyn crate::traits::ServiceAccount>> {
1335        use alien_core::bindings::ServiceAccountBinding;
1336
1337        let binding_json = self.bindings.get(binding_name).ok_or_else(|| {
1338            AlienError::new(ErrorData::BindingConfigInvalid {
1339                binding_name: binding_name.to_string(),
1340                reason: "Binding not found".to_string(),
1341            })
1342        })?;
1343
1344        let binding: ServiceAccountBinding = serde_json::from_value(binding_json.clone())
1345            .into_alien_error()
1346            .context(ErrorData::BindingConfigInvalid {
1347                binding_name: binding_name.to_string(),
1348                reason: "Failed to parse service account binding".to_string(),
1349            })?;
1350
1351        match binding {
1352            #[cfg(feature = "aws")]
1353            ServiceAccountBinding::AwsIam(aws_binding) => {
1354                use crate::providers::service_account::aws_iam::AwsIamServiceAccount;
1355
1356                let aws_config = self.client_config.aws_config().ok_or_else(|| {
1357                    AlienError::new(ErrorData::ClientConfigInvalid {
1358                        platform: Platform::Aws,
1359                        message: "AWS config not available".to_string(),
1360                    })
1361                })?;
1362                let client = crate::http_client::create_http_client();
1363
1364                let service_account_impl =
1365                    AwsIamServiceAccount::new(client, aws_config.clone(), aws_binding);
1366                let service_account: Arc<dyn crate::traits::ServiceAccount> =
1367                    Arc::new(service_account_impl);
1368                Ok(service_account)
1369            }
1370            #[cfg(not(feature = "aws"))]
1371            ServiceAccountBinding::AwsIam(_) => {
1372                Err(AlienError::new(ErrorData::FeatureNotEnabled {
1373                    feature: "aws".to_string(),
1374                }))
1375            }
1376
1377            #[cfg(feature = "gcp")]
1378            ServiceAccountBinding::GcpServiceAccount(gcp_binding) => {
1379                use crate::providers::service_account::gcp_service_account::GcpServiceAccount;
1380
1381                let gcp_config = self.client_config.gcp_config().ok_or_else(|| {
1382                    AlienError::new(ErrorData::ClientConfigInvalid {
1383                        platform: Platform::Gcp,
1384                        message: "GCP config not available".to_string(),
1385                    })
1386                })?;
1387                let client = crate::http_client::create_http_client();
1388
1389                let service_account_impl =
1390                    GcpServiceAccount::new(client, gcp_config.clone(), gcp_binding);
1391                let service_account: Arc<dyn crate::traits::ServiceAccount> =
1392                    Arc::new(service_account_impl);
1393                Ok(service_account)
1394            }
1395            #[cfg(not(feature = "gcp"))]
1396            ServiceAccountBinding::GcpServiceAccount(_) => {
1397                Err(AlienError::new(ErrorData::FeatureNotEnabled {
1398                    feature: "gcp".to_string(),
1399                }))
1400            }
1401
1402            #[cfg(feature = "azure")]
1403            ServiceAccountBinding::AzureManagedIdentity(azure_binding) => {
1404                use crate::providers::service_account::azure_managed_identity::AzureManagedIdentityServiceAccount;
1405
1406                let azure_config = self.client_config.azure_config().ok_or_else(|| {
1407                    AlienError::new(ErrorData::ClientConfigInvalid {
1408                        platform: Platform::Azure,
1409                        message: "Azure config not available".to_string(),
1410                    })
1411                })?;
1412
1413                let service_account_impl =
1414                    AzureManagedIdentityServiceAccount::new(azure_config.clone(), azure_binding);
1415                let service_account: Arc<dyn crate::traits::ServiceAccount> =
1416                    Arc::new(service_account_impl);
1417                Ok(service_account)
1418            }
1419            #[cfg(not(feature = "azure"))]
1420            ServiceAccountBinding::AzureManagedIdentity(_) => {
1421                Err(AlienError::new(ErrorData::FeatureNotEnabled {
1422                    feature: "azure".to_string(),
1423                }))
1424            }
1425        }
1426    }
1427}
1428
1429/// Conversion functions between SDK types and alien-core types
1430#[cfg(feature = "platform-sdk")]
1431mod conversions {
1432    use super::*;
1433    use serde::Serialize;
1434
1435    /// Convert SDK AgentStackState to alien-core StackState
1436    /// Generic over any serializable type since we convert via JSON
1437    pub fn convert_stack_state<T: Serialize>(sdk_stack_state: &T) -> Result<StackState> {
1438        // Convert via JSON serialization/deserialization (same pattern as deploy.rs)
1439        let stack_state: StackState = serde_json::from_value(
1440            serde_json::to_value(sdk_stack_state)
1441                .into_alien_error()
1442                .context(ErrorData::BindingConfigInvalid {
1443                    binding_name: "stack_state".to_string(),
1444                    reason: "Failed to serialize SDK stack state".to_string(),
1445                })?,
1446        )
1447        .into_alien_error()
1448        .context(ErrorData::BindingConfigInvalid {
1449            binding_name: "stack_state".to_string(),
1450            reason: "Failed to parse stack state".to_string(),
1451        })?;
1452
1453        Ok(stack_state)
1454    }
1455}