Skip to main content

pipedash_plugin_api/
plugin.rs

1use std::collections::HashMap;
2
3use async_trait::async_trait;
4
5use crate::actions::{
6    PipelineActionState,
7    RunActionState,
8    TriggerResult,
9};
10use crate::error::PluginResult;
11use crate::health::{
12    ClientOptions,
13    ComponentHealth,
14    StatusSource,
15};
16use crate::logs::{
17    LogCursor,
18    LogDialect,
19    LogNode,
20    LogRead,
21    LogSink,
22    PlainDialect,
23    RunRef,
24};
25use crate::metadata::PluginMetadata;
26use crate::presentation::{
27    PipelineFacts,
28    RunDisplay,
29    RunFacts,
30};
31use crate::sign_in::{
32    SignInClient,
33    SignInRefreshRejection,
34};
35use crate::support::retain_matching;
36use crate::types::*;
37use crate::utils::selected_items;
38
39pub mod defaults;
40
41#[async_trait]
42pub trait Plugin: Send + Sync {
43    fn metadata(&self) -> &PluginMetadata;
44
45    fn initialize(
46        &mut self, provider_id: i64, config: HashMap<String, String>,
47        http_client: Option<std::sync::Arc<reqwest::Client>>,
48    ) -> PluginResult<()>;
49
50    fn client_options(&self, _config: &HashMap<String, String>) -> ClientOptions {
51        ClientOptions::default()
52    }
53
54    fn status_source(&self) -> Option<&StatusSource> {
55        None
56    }
57
58    async fn check_health(&self) -> PluginResult<Vec<ComponentHealth>> {
59        Ok(Vec::new())
60    }
61
62    async fn validate_credentials(&self) -> PluginResult<bool>;
63
64    async fn fetch_available_pipelines(
65        &self, params: Option<crate::types::PaginationParams>,
66    ) -> PluginResult<crate::types::PaginatedAvailablePipelines> {
67        let _ = params;
68        Ok(crate::types::PaginatedResponse::empty())
69    }
70
71    async fn fetch_organizations(&self) -> PluginResult<Vec<crate::types::Organization>> {
72        Ok(Vec::new())
73    }
74
75    async fn fetch_pipeline_catalog(&self) -> PluginResult<crate::types::PipelineCatalog> {
76        Err(crate::error::PluginError::NotSupported(
77            "Pipeline catalog not supported by this provider".to_string(),
78        ))
79    }
80
81    async fn ids_with_ci(&self, ids: Vec<String>) -> PluginResult<Vec<String>> {
82        Ok(ids)
83    }
84
85    fn log_dialect(&self) -> &'static dyn LogDialect {
86        &PlainDialect
87    }
88
89    fn log_tree_version(&self) -> u32 {
90        0
91    }
92
93    async fn fetch_log_nodes(&self, _run: RunRef<'_>) -> PluginResult<Vec<LogNode>> {
94        Err(crate::error::PluginError::NotSupported(
95            "Run logs not supported by this provider".to_string(),
96        ))
97    }
98
99    async fn read_log(
100        &self, _run: RunRef<'_>, _node_id: &str, _from: Option<&LogCursor>, _sink: &mut LogSink,
101    ) -> PluginResult<LogRead> {
102        Err(crate::error::PluginError::NotSupported(
103            "Run logs not supported by this provider".to_string(),
104        ))
105    }
106
107    async fn fetch_available_pipelines_filtered(
108        &self, org: Option<String>, search: Option<String>,
109        params: Option<crate::types::PaginationParams>,
110    ) -> PluginResult<crate::types::PaginatedAvailablePipelines> {
111        let response = self.fetch_available_pipelines(params.clone()).await?;
112
113        let mut filtered_items = response.items;
114
115        if let Some(org_filter) = org {
116            filtered_items.retain(|p| p.organization.as_ref() == Some(&org_filter));
117        }
118
119        if let Some(search_term) = search {
120            retain_matching(&mut filtered_items, &search_term);
121        }
122
123        let total_count = filtered_items.len();
124        let params = params.unwrap_or_default();
125
126        Ok(crate::types::PaginatedResponse::new(
127            filtered_items,
128            params.page,
129            params.page_size,
130            total_count,
131        ))
132    }
133
134    async fn fetch_pipelines(&self) -> PluginResult<Vec<Pipeline>>;
135
136    async fn fetch_run_history(
137        &self, pipeline_id: &str, limit: usize,
138    ) -> PluginResult<Vec<PipelineRun>>;
139
140    async fn fetch_run_details(
141        &self, pipeline_id: &str, run_number: i64,
142    ) -> PluginResult<PipelineRun>;
143
144    async fn fetch_recent_runs(
145        &self, _pipeline_ids: &[String], _limit: usize,
146    ) -> PluginResult<HashMap<String, Vec<PipelineRun>>> {
147        Err(crate::error::PluginError::NotSupported(
148            "Batched run history not supported by this provider".to_string(),
149        ))
150    }
151
152    async fn trigger_pipeline(&self, params: TriggerParams) -> PluginResult<TriggerResult>;
153
154    async fn cancel_run(&self, _pipeline_id: &str, _run_number: i64) -> PluginResult<()> {
155        Err(crate::error::PluginError::NotSupported(
156            "Run cancellation not supported by this provider".to_string(),
157        ))
158    }
159
160    async fn force_cancel_run(&self, _pipeline_id: &str, _run_number: i64) -> PluginResult<()> {
161        Err(crate::error::PluginError::NotSupported(
162            "Force cancellation not supported by this provider".to_string(),
163        ))
164    }
165
166    async fn review_run(
167        &self, _pipeline_id: &str, _run_number: i64, _decision: ReviewDecision,
168    ) -> PluginResult<()> {
169        Err(crate::error::PluginError::NotSupported(
170            "Run review not supported by this provider".to_string(),
171        ))
172    }
173
174    async fn rerun_failed_jobs(&self, _pipeline_id: &str, _run_number: i64) -> PluginResult<()> {
175        Err(crate::error::PluginError::NotSupported(
176            "Re-running failed jobs not supported by this provider".to_string(),
177        ))
178    }
179
180    async fn rerun_run(&self, _pipeline_id: &str, _run_number: i64) -> PluginResult<()> {
181        Err(crate::error::PluginError::NotSupported(
182            "Re-running runs not supported by this provider".to_string(),
183        ))
184    }
185
186    async fn enable_pipeline(&self, _pipeline_id: &str) -> PluginResult<()> {
187        Err(crate::error::PluginError::NotSupported(
188            "Enabling pipelines not supported by this provider".to_string(),
189        ))
190    }
191
192    async fn fetch_workflow_parameters(
193        &self, _workflow_id: &str,
194    ) -> PluginResult<Vec<WorkflowParameter>> {
195        Ok(Vec::new())
196    }
197
198    async fn fetch_rate_limit(&self) -> PluginResult<Option<RateLimit>> {
199        Ok(None)
200    }
201
202    async fn get_field_options(
203        &self, _field_key: &str, _config: &HashMap<String, String>,
204    ) -> PluginResult<Vec<String>> {
205        Ok(Vec::new())
206    }
207
208    async fn sign_in_client(
209        &self, _config: &HashMap<String, String>, _http_client: &reqwest::Client,
210    ) -> PluginResult<SignInClient> {
211        Err(crate::error::PluginError::NotSupported(
212            "Sign-in is not supported by this provider".to_string(),
213        ))
214    }
215
216    fn sign_in_refresh_rejections(&self) -> &'static [SignInRefreshRejection] {
217        &[]
218    }
219
220    async fn check_permissions(&self) -> PluginResult<PermissionStatus> {
221        let required_permissions = &self.metadata().required_permissions;
222        let permissions = required_permissions
223            .iter()
224            .map(|p| PermissionCheck {
225                permission: p.clone(),
226                granted: true,
227            })
228            .collect();
229
230        Ok(PermissionStatus {
231            permissions,
232            all_granted: true,
233            checked_at: chrono::Utc::now(),
234            metadata: std::collections::HashMap::new(),
235            credential_label: None,
236        })
237    }
238
239    fn get_feature_availability(&self, status: &PermissionStatus) -> Vec<FeatureAvailability> {
240        let features = &self.metadata().features;
241        let granted_perms: std::collections::HashSet<String> = status
242            .permissions
243            .iter()
244            .filter(|p| p.granted)
245            .map(|p| p.permission.name.clone())
246            .collect();
247
248        features
249            .iter()
250            .map(|feature| {
251                let missing: Vec<String> = feature
252                    .required_permissions
253                    .iter()
254                    .filter(|p| !granted_perms.contains(*p))
255                    .cloned()
256                    .collect();
257
258                FeatureAvailability {
259                    feature: feature.clone(),
260                    available: missing.is_empty(),
261                    missing_permissions: missing,
262                }
263            })
264            .collect()
265    }
266
267    fn can_archive_run(
268        &self, _pipeline_metadata: &HashMap<String, serde_json::Value>,
269        _run_metadata: &HashMap<String, serde_json::Value>, _status: PipelineStatus,
270    ) -> bool {
271        false
272    }
273
274    fn selection_required(&self, _config: &HashMap<String, String>) -> bool {
275        true
276    }
277
278    fn configured_repositories(&self, config: &HashMap<String, String>) -> Vec<String> {
279        selected_items(config).map(str::to_string).collect()
280    }
281
282    fn pipeline_actions(&self, pipeline: &PipelineFacts<'_>) -> PipelineActionState {
283        defaults::pipeline_actions(&self.metadata().capabilities, pipeline)
284    }
285
286    fn run_actions(&self, _pipeline: &PipelineFacts<'_>, run: &RunFacts<'_>) -> RunActionState {
287        defaults::run_actions(&self.metadata().capabilities, run)
288    }
289
290    fn run_display(&self, _pipeline: &PipelineFacts<'_>, run: &RunFacts<'_>) -> RunDisplay {
291        defaults::run_display(run)
292    }
293
294    fn trigger_values(
295        &self, _pipeline: &PipelineFacts<'_>, previous: Option<&RunFacts<'_>>,
296        parameters: &[WorkflowParameter],
297    ) -> serde_json::Map<String, serde_json::Value> {
298        defaults::trigger_values(previous, parameters)
299    }
300}
301
302#[cfg(test)]
303mod tests;