Skip to main content

pipedash_plugin_api/
plugin.rs

1use std::collections::HashMap;
2
3use async_trait::async_trait;
4use serde::{
5    Deserialize,
6    Serialize,
7};
8
9use crate::error::PluginResult;
10use crate::health::{
11    ClientOptions,
12    ComponentHealth,
13    StatusSource,
14};
15use crate::schema::{
16    ConfigSchema,
17    TableSchema,
18};
19use crate::types::*;
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct PluginMetadata {
23    pub name: String,
24    pub provider_type: String,
25    pub version: String,
26    pub description: String,
27    pub author: Option<String>,
28    pub icon: Option<String>,
29    pub config_schema: ConfigSchema,
30    pub table_schema: TableSchema,
31    pub capabilities: PluginCapabilities,
32    #[serde(default)]
33    pub required_permissions: Vec<Permission>,
34    #[serde(default)]
35    pub features: Vec<Feature>,
36}
37
38#[derive(Debug, Clone, Serialize, Deserialize, Default)]
39pub struct PluginCapabilities {
40    pub pipelines: bool,
41    pub pipeline_runs: bool,
42    pub trigger: bool,
43    pub cancel: bool,
44    pub agents: bool,
45    pub artifacts: bool,
46    pub queues: bool,
47    pub custom_tables: bool,
48    #[serde(default)]
49    pub retains_run_history: bool,
50}
51
52#[async_trait]
53pub trait Plugin: Send + Sync {
54    fn metadata(&self) -> &PluginMetadata;
55
56    fn initialize(
57        &mut self, provider_id: i64, config: HashMap<String, String>,
58        http_client: Option<std::sync::Arc<reqwest::Client>>,
59    ) -> PluginResult<()>;
60
61    fn client_options(&self, _config: &HashMap<String, String>) -> ClientOptions {
62        ClientOptions::default()
63    }
64
65    fn status_source(&self) -> Option<&StatusSource> {
66        None
67    }
68
69    async fn check_health(&self) -> PluginResult<Vec<ComponentHealth>> {
70        Ok(Vec::new())
71    }
72
73    async fn validate_credentials(&self) -> PluginResult<bool>;
74
75    async fn fetch_available_pipelines(
76        &self, params: Option<crate::types::PaginationParams>,
77    ) -> PluginResult<crate::types::PaginatedAvailablePipelines> {
78        let _ = params;
79        Ok(crate::types::PaginatedResponse::empty())
80    }
81
82    async fn fetch_organizations(&self) -> PluginResult<Vec<crate::types::Organization>> {
83        Ok(Vec::new())
84    }
85
86    async fn fetch_available_pipelines_filtered(
87        &self, org: Option<String>, search: Option<String>,
88        params: Option<crate::types::PaginationParams>,
89    ) -> PluginResult<crate::types::PaginatedAvailablePipelines> {
90        let response = self.fetch_available_pipelines(params.clone()).await?;
91
92        let mut filtered_items = response.items;
93
94        if let Some(org_filter) = org {
95            filtered_items.retain(|p| p.organization.as_ref() == Some(&org_filter));
96        }
97
98        if let Some(search_term) = search {
99            let search_lower = search_term.to_lowercase();
100            filtered_items.retain(|p| {
101                p.name.to_lowercase().contains(&search_lower)
102                    || p.id.to_lowercase().contains(&search_lower)
103                    || p.description
104                        .as_ref()
105                        .is_some_and(|d| d.to_lowercase().contains(&search_lower))
106            });
107        }
108
109        let total_count = filtered_items.len();
110        let params = params.unwrap_or_default();
111
112        Ok(crate::types::PaginatedResponse::new(
113            filtered_items,
114            params.page,
115            params.page_size,
116            total_count,
117        ))
118    }
119
120    async fn fetch_pipelines(&self) -> PluginResult<Vec<Pipeline>>;
121
122    async fn fetch_pipelines_paginated(
123        &self, page: usize, page_size: usize,
124    ) -> PluginResult<crate::types::PaginatedResponse<Pipeline>> {
125        let all_pipelines = self.fetch_pipelines().await?;
126        let total_count = all_pipelines.len();
127        let start = (page - 1) * page_size;
128        let end = start + page_size;
129
130        let pipelines = if start < total_count {
131            all_pipelines[start..end.min(total_count)].to_vec()
132        } else {
133            Vec::new()
134        };
135
136        Ok(crate::types::PaginatedResponse::new(
137            pipelines,
138            page,
139            page_size,
140            total_count,
141        ))
142    }
143
144    async fn fetch_run_history(
145        &self, pipeline_id: &str, limit: usize,
146    ) -> PluginResult<Vec<PipelineRun>>;
147
148    async fn fetch_run_details(
149        &self, pipeline_id: &str, run_number: i64,
150    ) -> PluginResult<PipelineRun>;
151
152    async fn trigger_pipeline(&self, params: TriggerParams) -> PluginResult<String>;
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 review_run(
161        &self, _pipeline_id: &str, _run_number: i64, _decision: ReviewDecision,
162    ) -> PluginResult<()> {
163        Err(crate::error::PluginError::NotSupported(
164            "Run review not supported by this provider".to_string(),
165        ))
166    }
167
168    async fn fetch_workflow_parameters(
169        &self, _workflow_id: &str,
170    ) -> PluginResult<Vec<WorkflowParameter>> {
171        Ok(Vec::new())
172    }
173
174    async fn fetch_agents(&self) -> PluginResult<Vec<BuildAgent>> {
175        Err(crate::error::PluginError::NotSupported(
176            "Agent monitoring not supported by this provider".to_string(),
177        ))
178    }
179
180    async fn fetch_artifacts(&self, _run_id: &str) -> PluginResult<Vec<BuildArtifact>> {
181        Err(crate::error::PluginError::NotSupported(
182            "Artifact fetching not supported by this provider".to_string(),
183        ))
184    }
185
186    async fn fetch_queues(&self) -> PluginResult<Vec<BuildQueue>> {
187        Err(crate::error::PluginError::NotSupported(
188            "Queue monitoring not supported by this provider".to_string(),
189        ))
190    }
191
192    async fn fetch_rate_limit(&self) -> PluginResult<Option<RateLimit>> {
193        Ok(None)
194    }
195
196    fn get_migrations(&self) -> Vec<String> {
197        Vec::new()
198    }
199
200    fn provider_type(&self) -> &str {
201        &self.metadata().provider_type
202    }
203
204    async fn get_field_options(
205        &self, _field_key: &str, _config: &HashMap<String, String>,
206    ) -> PluginResult<Vec<String>> {
207        Ok(Vec::new())
208    }
209
210    async fn check_permissions(&self) -> PluginResult<PermissionStatus> {
211        let required_permissions = &self.metadata().required_permissions;
212        let permissions = required_permissions
213            .iter()
214            .map(|p| PermissionCheck {
215                permission: p.clone(),
216                granted: true,
217            })
218            .collect();
219
220        Ok(PermissionStatus {
221            permissions,
222            all_granted: true,
223            checked_at: chrono::Utc::now(),
224            metadata: std::collections::HashMap::new(),
225        })
226    }
227
228    fn get_feature_availability(&self, status: &PermissionStatus) -> Vec<FeatureAvailability> {
229        let features = &self.metadata().features;
230        let granted_perms: std::collections::HashSet<String> = status
231            .permissions
232            .iter()
233            .filter(|p| p.granted)
234            .map(|p| p.permission.name.clone())
235            .collect();
236
237        features
238            .iter()
239            .map(|feature| {
240                let missing: Vec<String> = feature
241                    .required_permissions
242                    .iter()
243                    .filter(|p| !granted_perms.contains(*p))
244                    .cloned()
245                    .collect();
246
247                FeatureAvailability {
248                    feature: feature.clone(),
249                    available: missing.is_empty(),
250                    missing_permissions: missing,
251                }
252            })
253            .collect()
254    }
255}