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;