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