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