Skip to main content

pipedash_core/infrastructure/providers/
plugin_adapter.rs

1use std::sync::Arc;
2
3use async_trait::async_trait;
4use chrono::{
5    DateTime,
6    Utc,
7};
8use pipedash_plugin_api::{
9    Plugin as PluginTrait,
10    RunRef,
11};
12
13use crate::domain::{
14    DomainError,
15    DomainResult,
16    LogCursor,
17    LogDialect,
18    LogNode,
19    LogRead,
20    LogSink,
21    Pipeline,
22    PipelineRun,
23    Provider,
24    TriggerParams,
25};
26
27pub struct PluginAdapter {
28    plugin: Arc<dyn PluginTrait>,
29    provider_type: String,
30    provider_id: i64,
31    renew_at: Option<DateTime<Utc>>,
32}
33
34impl PluginAdapter {
35    pub fn new(
36        plugin: Box<dyn PluginTrait>, provider_type: String, provider_id: i64,
37        renew_at: Option<DateTime<Utc>>,
38    ) -> Self {
39        Self {
40            plugin: Arc::from(plugin),
41            provider_type,
42            provider_id,
43            renew_at,
44        }
45    }
46
47    pub(crate) fn map_error(e: pipedash_plugin_api::PluginError) -> DomainError {
48        match e {
49            pipedash_plugin_api::PluginError::AuthenticationFailed(msg) => {
50                DomainError::ProviderAuthenticationFailed(msg)
51            }
52            pipedash_plugin_api::PluginError::PermissionDenied(msg) => {
53                DomainError::PermissionDenied(msg)
54            }
55            pipedash_plugin_api::PluginError::ApiError(msg) => DomainError::ApiError(msg),
56            pipedash_plugin_api::PluginError::InvalidConfig(msg) => DomainError::InvalidConfig(msg),
57            pipedash_plugin_api::PluginError::PipelineNotFound(msg) => {
58                DomainError::PipelineNotFound(msg)
59            }
60            pipedash_plugin_api::PluginError::ProviderNotSupported(msg) => {
61                DomainError::InvalidProviderType(msg)
62            }
63            pipedash_plugin_api::PluginError::NetworkError(msg) => DomainError::NetworkError(msg),
64            pipedash_plugin_api::PluginError::SerializationError(msg) => {
65                DomainError::ApiError(format!("Serialization error: {}", msg))
66            }
67            pipedash_plugin_api::PluginError::DatabaseError(msg) => DomainError::DatabaseError(msg),
68            pipedash_plugin_api::PluginError::NotSupported(msg) => DomainError::NotSupported(msg),
69            pipedash_plugin_api::PluginError::Internal(msg) => DomainError::InternalError(msg),
70            pipedash_plugin_api::PluginError::RateLimited {
71                message,
72                retry_after,
73            } => DomainError::RateLimited {
74                message,
75                retry_after,
76            },
77            pipedash_plugin_api::PluginError::ServiceUnavailable {
78                message,
79                retry_after,
80            } => DomainError::ServiceUnavailable {
81                message,
82                retry_after,
83            },
84            pipedash_plugin_api::PluginError::Tls(msg) => DomainError::Tls(msg),
85            e => DomainError::ApiError(e.to_string()),
86        }
87    }
88
89    fn convert_pipeline(
90        plugin_pipeline: pipedash_plugin_api::Pipeline, provider_id: i64, provider_type: &str,
91    ) -> Pipeline {
92        Pipeline {
93            id: plugin_pipeline.id,
94            provider_id,
95            provider_type: provider_type.to_string(),
96            name: plugin_pipeline.name,
97            status: Self::convert_status(plugin_pipeline.status),
98            last_run: plugin_pipeline.last_run,
99            last_updated: plugin_pipeline.last_updated,
100            repository: plugin_pipeline.repository,
101            branch: plugin_pipeline.branch,
102            workflow_file: plugin_pipeline.workflow_file,
103            resource_state: plugin_pipeline.resource_state,
104            metadata: plugin_pipeline.metadata,
105            overdue_run: None,
106            typical_duration_seconds: None,
107        }
108    }
109
110    fn convert_status(
111        status: pipedash_plugin_api::PipelineStatus,
112    ) -> crate::domain::PipelineStatus {
113        match status {
114            pipedash_plugin_api::PipelineStatus::Success => crate::domain::PipelineStatus::Success,
115            pipedash_plugin_api::PipelineStatus::Failed => crate::domain::PipelineStatus::Failed,
116            pipedash_plugin_api::PipelineStatus::Degraded => {
117                crate::domain::PipelineStatus::Degraded
118            }
119            pipedash_plugin_api::PipelineStatus::Running => crate::domain::PipelineStatus::Running,
120            pipedash_plugin_api::PipelineStatus::Pending => crate::domain::PipelineStatus::Pending,
121            pipedash_plugin_api::PipelineStatus::Cancelled => {
122                crate::domain::PipelineStatus::Cancelled
123            }
124            pipedash_plugin_api::PipelineStatus::Skipped => crate::domain::PipelineStatus::Skipped,
125        }
126    }
127
128    fn convert_run(plugin_run: pipedash_plugin_api::PipelineRun) -> PipelineRun {
129        PipelineRun {
130            id: plugin_run.id,
131            pipeline_id: plugin_run.pipeline_id,
132            run_number: plugin_run.run_number,
133            status: Self::convert_status(plugin_run.status),
134            started_at: plugin_run.started_at,
135            concluded_at: plugin_run.concluded_at,
136            duration_seconds: plugin_run.duration_seconds,
137            logs_url: plugin_run.logs_url,
138            commit_sha: plugin_run.commit_sha,
139            commit_message: plugin_run.commit_message,
140            branch: plugin_run.branch,
141            actor: plugin_run.actor,
142            inputs: plugin_run.inputs,
143            metadata: plugin_run.metadata,
144        }
145    }
146
147    fn run_ref(run: &PipelineRun) -> RunRef<'_> {
148        RunRef {
149            pipeline_id: &run.pipeline_id,
150            run_number: run.run_number,
151            run_id: &run.id,
152            metadata: &run.metadata,
153        }
154    }
155}
156
157#[async_trait]
158impl Provider for PluginAdapter {
159    async fn fetch_pipelines(&self) -> DomainResult<Vec<Pipeline>> {
160        let pipelines = self
161            .plugin
162            .fetch_pipelines()
163            .await
164            .map_err(Self::map_error)?;
165
166        Ok(pipelines
167            .into_iter()
168            .map(|p| Self::convert_pipeline(p, self.provider_id, &self.provider_type))
169            .collect())
170    }
171
172    async fn fetch_run_history(
173        &self, pipeline_id: &str, limit: usize,
174    ) -> DomainResult<Vec<PipelineRun>> {
175        let runs = self
176            .plugin
177            .fetch_run_history(pipeline_id, limit)
178            .await
179            .map_err(Self::map_error)?;
180
181        Ok(runs.into_iter().map(Self::convert_run).collect())
182    }
183
184    async fn fetch_run_details(
185        &self, pipeline_id: &str, run_number: i64,
186    ) -> DomainResult<PipelineRun> {
187        let run = self
188            .plugin
189            .fetch_run_details(pipeline_id, run_number)
190            .await
191            .map_err(Self::map_error)?;
192
193        Ok(Self::convert_run(run))
194    }
195
196    async fn trigger_pipeline(&self, params: TriggerParams) -> DomainResult<String> {
197        let trigger_params = pipedash_plugin_api::TriggerParams {
198            workflow_id: params.workflow_id,
199            inputs: params.inputs,
200        };
201
202        self.plugin
203            .trigger_pipeline(trigger_params)
204            .await
205            .map_err(Self::map_error)
206    }
207
208    async fn cancel_run(&self, pipeline_id: &str, run_number: i64) -> DomainResult<()> {
209        self.plugin
210            .cancel_run(pipeline_id, run_number)
211            .await
212            .map_err(Self::map_error)
213    }
214
215    async fn review_run(
216        &self, pipeline_id: &str, run_number: i64, decision: pipedash_plugin_api::ReviewDecision,
217    ) -> DomainResult<()> {
218        self.plugin
219            .review_run(pipeline_id, run_number, decision)
220            .await
221            .map_err(Self::map_error)
222    }
223
224    async fn rerun_failed_jobs(&self, pipeline_id: &str, run_number: i64) -> DomainResult<()> {
225        self.plugin
226            .rerun_failed_jobs(pipeline_id, run_number)
227            .await
228            .map_err(Self::map_error)
229    }
230
231    async fn rerun_run(&self, pipeline_id: &str, run_number: i64) -> DomainResult<()> {
232        self.plugin
233            .rerun_run(pipeline_id, run_number)
234            .await
235            .map_err(Self::map_error)
236    }
237
238    async fn get_workflow_parameters(
239        &self, workflow_id: &str,
240    ) -> DomainResult<Vec<pipedash_plugin_api::WorkflowParameter>> {
241        self.plugin
242            .fetch_workflow_parameters(workflow_id)
243            .await
244            .map_err(Self::map_error)
245    }
246
247    async fn fetch_rate_limit(&self) -> DomainResult<Option<pipedash_plugin_api::RateLimit>> {
248        self.plugin
249            .fetch_rate_limit()
250            .await
251            .map_err(Self::map_error)
252    }
253
254    async fn validate_credentials(&self) -> DomainResult<bool> {
255        self.plugin
256            .validate_credentials()
257            .await
258            .map_err(Self::map_error)
259    }
260
261    fn provider_type(&self) -> &str {
262        &self.provider_type
263    }
264
265    fn capabilities(&self) -> &pipedash_plugin_api::PluginCapabilities {
266        &self.plugin.metadata().capabilities
267    }
268
269    fn renew_at(&self) -> Option<DateTime<Utc>> {
270        self.renew_at
271    }
272
273    fn status_source(&self) -> Option<&pipedash_plugin_api::StatusSource> {
274        self.plugin.status_source()
275    }
276
277    async fn check_health(&self) -> DomainResult<Vec<pipedash_plugin_api::ComponentHealth>> {
278        self.plugin.check_health().await.map_err(Self::map_error)
279    }
280
281    fn log_dialect(&self) -> &'static dyn LogDialect {
282        self.plugin.log_dialect()
283    }
284
285    async fn fetch_log_nodes(&self, run: &PipelineRun) -> DomainResult<Vec<LogNode>> {
286        let nodes = self
287            .plugin
288            .fetch_log_nodes(Self::run_ref(run))
289            .await
290            .map_err(Self::map_error)?;
291        Ok(nodes
292            .into_iter()
293            .map(|n| LogNode {
294                id: n.id,
295                parent_id: n.parent_id,
296                kind: n.kind,
297                name: n.name,
298                status: Self::convert_status(n.status),
299                started_at: n.started_at,
300                finished_at: n.finished_at,
301                log: n.log,
302                web_url: n.web_url,
303            })
304            .collect())
305    }
306
307    async fn read_log(
308        &self, run: &PipelineRun, node_id: &str, from: Option<&LogCursor>, sink: &mut LogSink,
309    ) -> DomainResult<LogRead> {
310        self.plugin
311            .read_log(Self::run_ref(run), node_id, from, sink)
312            .await
313            .map_err(Self::map_error)
314    }
315}