Skip to main content

alien_bindings/providers/build/
grpc.rs

1use crate::{
2    error::{Error, ErrorData},
3    grpc::build_service::alien_bindings::build::{
4        build_service_client::BuildServiceClient, BuildConfig as ProtoBuildConfig,
5        ComputeType as ProtoComputeType, GetBuildStatusRequest, StartBuildRequest,
6        StopBuildRequest,
7    },
8    grpc::status_conversion::status_to_alien_error,
9    traits::Build,
10};
11use alien_core::{BuildConfig, BuildExecution, BuildStatus};
12use alien_error::{AlienError, Context, IntoAlienError};
13use async_trait::async_trait;
14use tonic::{transport::Channel, Request, Status};
15
16/// gRPC implementation of the `Build` trait.
17///
18/// This implementation communicates with an alien-runtime gRPC server
19/// to manage build operations.
20#[derive(Debug)]
21pub struct GrpcBuild {
22    client: BuildServiceClient<Channel>,
23    binding_name: String,
24}
25
26impl GrpcBuild {
27    /// Creates a new gRPC build instance from binding parameters.
28    pub async fn new(binding_name: String, grpc_address: String) -> Result<Self, Error> {
29        let channel = crate::providers::grpc_provider::create_grpc_channel(grpc_address).await?;
30        Self::new_from_channel(channel, binding_name).await
31    }
32
33    /// Creates a new gRPC build instance from a channel.
34    pub async fn new_from_channel(channel: Channel, binding_name: String) -> Result<Self, Error> {
35        let client = BuildServiceClient::new(channel);
36
37        Ok(Self {
38            client,
39            binding_name,
40        })
41    }
42
43    fn client(&self) -> BuildServiceClient<Channel> {
44        self.client.clone()
45    }
46}
47
48fn map_alien_config_to_proto(config: BuildConfig) -> ProtoBuildConfig {
49    let compute_type = match config.compute_type {
50        alien_core::ComputeType::Small => ProtoComputeType::Small,
51        alien_core::ComputeType::Medium => ProtoComputeType::Medium,
52        alien_core::ComputeType::Large => ProtoComputeType::Large,
53        alien_core::ComputeType::XLarge => ProtoComputeType::Xlarge,
54    };
55
56    // Convert monitoring configuration if present
57    let monitoring = if let Some(alien_monitoring) = config.monitoring {
58        Some(
59            crate::grpc::build_service::alien_bindings::build::MonitoringConfig {
60                endpoint: alien_monitoring.endpoint,
61                headers: alien_monitoring.headers,
62                logs_uri: alien_monitoring.logs_uri,
63                tls_enabled: alien_monitoring.tls_enabled,
64                tls_verify: alien_monitoring.tls_verify,
65            },
66        )
67    } else {
68        None
69    };
70
71    ProtoBuildConfig {
72        script: config.script,
73        environment: config.environment,
74        compute_type: compute_type.into(),
75        timeout_seconds: Some(config.timeout_seconds as i32),
76        monitoring,
77    }
78}
79
80fn map_proto_execution_to_alien(
81    execution: crate::grpc::build_service::alien_bindings::build::BuildExecution,
82) -> BuildExecution {
83    let status = match execution.status() {
84        crate::grpc::build_service::alien_bindings::build::BuildStatus::Unspecified => {
85            BuildStatus::Failed
86        }
87        crate::grpc::build_service::alien_bindings::build::BuildStatus::Queued => {
88            BuildStatus::Queued
89        }
90        crate::grpc::build_service::alien_bindings::build::BuildStatus::Running => {
91            BuildStatus::Running
92        }
93        crate::grpc::build_service::alien_bindings::build::BuildStatus::Succeeded => {
94            BuildStatus::Succeeded
95        }
96        crate::grpc::build_service::alien_bindings::build::BuildStatus::Failed => {
97            BuildStatus::Failed
98        }
99        crate::grpc::build_service::alien_bindings::build::BuildStatus::Cancelled => {
100            BuildStatus::Cancelled
101        }
102        crate::grpc::build_service::alien_bindings::build::BuildStatus::TimedOut => {
103            BuildStatus::TimedOut
104        }
105    };
106
107    BuildExecution {
108        id: execution.id,
109        status,
110        start_time: execution.start_time,
111        end_time: execution.end_time,
112    }
113}
114
115#[async_trait]
116impl Build for GrpcBuild {
117    async fn start_build(&self, config: BuildConfig) -> Result<BuildExecution, Error> {
118        let mut client = self.client();
119
120        let proto_config = map_alien_config_to_proto(config);
121        let request = StartBuildRequest {
122            binding_name: self.binding_name.clone(),
123            config: Some(proto_config),
124        };
125
126        let response = client
127            .start_build(Request::new(request))
128            .await
129            .map_err(|e| status_to_alien_error(e, "start_build"))?
130            .into_inner();
131
132        let execution = response.execution.ok_or_else(|| {
133            AlienError::new(ErrorData::UnexpectedResponseFormat {
134                provider: "grpc".to_string(),
135                binding_name: self.binding_name.clone(),
136                field: "execution".to_string(),
137                response_json: "missing execution field".to_string(),
138            })
139        })?;
140
141        Ok(map_proto_execution_to_alien(execution))
142    }
143
144    async fn get_build_status(&self, build_id: &str) -> Result<BuildExecution, Error> {
145        let mut client = self.client();
146
147        let request = GetBuildStatusRequest {
148            binding_name: self.binding_name.clone(),
149            build_id: build_id.to_string(),
150        };
151
152        let response = client
153            .get_build_status(Request::new(request))
154            .await
155            .map_err(|e| status_to_alien_error(e, "get_build_status"))?
156            .into_inner();
157
158        let execution = response.execution.ok_or_else(|| {
159            AlienError::new(ErrorData::UnexpectedResponseFormat {
160                provider: "grpc".to_string(),
161                binding_name: self.binding_name.clone(),
162                field: "execution".to_string(),
163                response_json: "missing execution field".to_string(),
164            })
165        })?;
166
167        Ok(map_proto_execution_to_alien(execution))
168    }
169
170    async fn stop_build(&self, build_id: &str) -> Result<(), Error> {
171        let mut client = self.client();
172
173        let request = StopBuildRequest {
174            binding_name: self.binding_name.clone(),
175            build_id: build_id.to_string(),
176        };
177
178        client
179            .stop_build(Request::new(request))
180            .await
181            .map_err(|e| status_to_alien_error(e, "stop_build"))?;
182
183        Ok(())
184    }
185}
186
187impl crate::traits::Binding for GrpcBuild {}