1use 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#[derive(Debug, Clone)]
24pub struct BindingsProvider {
25 client_config: ClientConfig,
26 bindings: HashMap<String, serde_json::Value>,
27 cache: Arc<RwLock<HashMap<String, Box<dyn Any + Send + Sync>>>>,
31}
32
33impl BindingsProvider {
34 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 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 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 pub async fn from_env(env: HashMap<String, String>) -> Result<Self> {
79 let platform = crate::get_platform_from_env(&env)?;
81
82 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 let bindings = Self::parse_bindings_from_env(&env)?;
92
93 Self::new(client_config, bindings)
94 }
95
96 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 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 #[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 let sdk_client = alien_platform_api::Client::new(base_url);
173
174 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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#[cfg(feature = "platform-sdk")]
1593mod conversions {
1594 use super::*;
1595 use serde::Serialize;
1596
1597 pub fn convert_stack_state<T: Serialize>(sdk_stack_state: &T) -> Result<StackState> {
1600 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}