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}