Skip to main content

alien_bindings/providers/service_account/
gcp_service_account.rs

1use crate::error::{binding_env_var, ErrorData, Result};
2use crate::traits::{
3    Binding, GcpServiceAccountInfo, ImpersonationRequest, ServiceAccount, ServiceAccountInfo,
4};
5use alien_core::bindings::GcpServiceAccountBinding;
6use alien_core::{ClientConfig, GcpClientConfig as CoreGcpClientConfig, GcpCredentials};
7use alien_error::Context;
8use alien_gcp_clients::{GcpClientConfig, GcpImpersonationConfig};
9use async_trait::async_trait;
10use reqwest::Client;
11
12/// GCP Service Account binding implementation
13#[derive(Debug)]
14pub struct GcpServiceAccount {
15    config: GcpClientConfig,
16    binding: GcpServiceAccountBinding,
17}
18
19impl GcpServiceAccount {
20    pub fn new(
21        http_client: Client,
22        config: GcpClientConfig,
23        binding: GcpServiceAccountBinding,
24    ) -> Self {
25        let _ = http_client;
26        Self { config, binding }
27    }
28
29    /// Get the service account email from the binding, resolving template expressions if needed
30    fn get_email(&self) -> Result<String> {
31        self.binding
32            .email
33            .clone()
34            .into_value("service-account", "email")
35            .context(ErrorData::BindingConfigInvalid {
36                env_var: binding_env_var("service-account"),
37                binding_name: "service-account".to_string(),
38                reason: "Failed to resolve email from binding".to_string(),
39            })
40    }
41
42    /// Get the unique ID from the binding, resolving template expressions if needed
43    fn get_unique_id(&self) -> Result<String> {
44        self.binding
45            .unique_id
46            .clone()
47            .into_value("service-account", "unique_id")
48            .context(ErrorData::BindingConfigInvalid {
49                env_var: binding_env_var("service-account"),
50                binding_name: "service-account".to_string(),
51                reason: "Failed to resolve unique_id from binding".to_string(),
52            })
53    }
54}
55
56impl Binding for GcpServiceAccount {}
57
58#[async_trait]
59impl ServiceAccount for GcpServiceAccount {
60    async fn get_info(&self) -> Result<ServiceAccountInfo> {
61        let email = self.get_email()?;
62        let unique_id = self.get_unique_id()?;
63
64        Ok(ServiceAccountInfo::Gcp(GcpServiceAccountInfo {
65            email,
66            unique_id,
67        }))
68    }
69
70    async fn impersonate(&self, request: ImpersonationRequest) -> Result<ClientConfig> {
71        let email = self.get_email()?;
72        let scopes = request
73            .scopes
74            .unwrap_or_else(|| vec!["https://www.googleapis.com/auth/cloud-platform".to_string()]);
75
76        let impersonated_config = CoreGcpClientConfig {
77            project_id: self.config.project_id.clone(),
78            region: self.config.region.clone(),
79            credentials: GcpCredentials::ImpersonatedServiceAccount {
80                source: Box::new(self.config.clone()),
81                config: GcpImpersonationConfig {
82                    service_account_email: email,
83                    scopes,
84                    delegates: None,
85                    lifetime: request
86                        .duration_seconds
87                        .map(|seconds| format!("{}s", seconds.clamp(1, 3600))),
88                    target_project_id: None,
89                    target_region: None,
90                },
91            },
92            service_overrides: self.config.service_overrides.clone(),
93            project_number: self.config.project_number.clone(),
94        };
95
96        Ok(ClientConfig::Gcp(Box::new(impersonated_config)))
97    }
98
99    fn as_any(&self) -> &dyn std::any::Any {
100        self
101    }
102}