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