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