bamboo_server/handlers/agent/plugin/
handlers.rs1use std::path::PathBuf;
11
12use actix_web::{web, HttpResponse, Responder};
13use bamboo_plugin::{InstallDisposition, PluginError, PluginInstaller, PluginSource};
14
15use crate::app_state::AppState;
16use crate::plugin_installer::ServerPluginInstaller;
17use crate::plugin_source::{install_plugin_from_source, stage_plugin_source, PluginSourceInput};
18
19use super::api_types::{to_view, InstallPluginRequest, PluginListResponse};
20use super::errors::plugin_error_response;
21
22fn plugins_root(state: &AppState) -> PathBuf {
23 state.app_data_dir.join("plugins")
24}
25
26fn to_source_input(source: PluginSource) -> PluginSourceInput {
42 match source {
43 PluginSource::LocalDir { path } => PluginSourceInput::LocalDir(path),
44 PluginSource::LocalArchive { path } => PluginSourceInput::LocalArchive(path),
45 PluginSource::Url {
46 url,
47 sha256,
48 allow_unverified,
49 allow_untrusted_host,
50 allow_unsigned,
51 signed_by: _,
52 insecure,
53 } => PluginSourceInput::Url {
54 url,
55 sha256,
56 allow_unverified,
57 allow_untrusted_host,
58 allow_unsigned,
59 insecure,
60 },
61 }
62}
63
64pub async fn list_plugins(state: web::Data<AppState>) -> impl Responder {
66 let installer = ServerPluginInstaller::new(state.clone());
67 match installer.list().await {
68 Ok(entries) => {
69 let mut plugins = Vec::with_capacity(entries.len());
70 for entry in entries {
71 plugins.push(to_view(entry, &state.service_manager).await);
72 }
73 HttpResponse::Ok().json(PluginListResponse { plugins })
74 }
75 Err(error) => plugin_error_response(&error),
76 }
77}
78
79pub async fn install_plugin(
83 state: web::Data<AppState>,
84 body: web::Json<InstallPluginRequest>,
85) -> impl Responder {
86 let installer = ServerPluginInstaller::new(state.clone());
87 let root = plugins_root(&state);
88 let input = to_source_input(body.into_inner().source);
89 let trust = state.config.read().await.plugin_trust.clone();
90
91 match install_plugin_from_source(
92 &installer,
93 input,
94 &root,
95 &trust,
96 InstallDisposition::FailIfInstalled,
97 )
98 .await
99 {
100 Ok(entry) => HttpResponse::Created().json(to_view(entry, &state.service_manager).await),
101 Err(error) => plugin_error_response(&error),
102 }
103}
104
105pub async fn update_plugin(
115 state: web::Data<AppState>,
116 path: web::Path<String>,
117 body: web::Json<InstallPluginRequest>,
118) -> impl Responder {
119 let path_id = path.into_inner();
120 let installer = ServerPluginInstaller::new(state.clone());
121 let root = plugins_root(&state);
122 let input = to_source_input(body.into_inner().source);
123 let trust = state.config.read().await.plugin_trust.clone();
124
125 let stopped_services = installer.stop_services_for_upgrade(&path_id).await;
137
138 let staged = match stage_plugin_source(input, &root, &trust).await {
139 Ok(staged) => staged,
140 Err(error) => {
141 installer
142 .restart_services_after_failed_upgrade(&path_id, &stopped_services)
143 .await;
144 return plugin_error_response(&error);
145 }
146 };
147 if staged.manifest.id != path_id {
148 let manifest_id = staged.manifest.id.clone();
149 staged.rollback().await;
150 installer
151 .restart_services_after_failed_upgrade(&path_id, &stopped_services)
152 .await;
153 return plugin_error_response(&PluginError::InvalidManifest(format!(
154 "path id '{path_id}' does not match the source's manifest id '{manifest_id}'"
155 )));
156 }
157
158 let manifest = staged.manifest.clone();
159 let plugin_dir = staged.plugin_dir.clone();
160 let source = staged.source.clone();
161 match installer
162 .install(
163 &manifest,
164 &plugin_dir,
165 source,
166 InstallDisposition::Upgrade,
167 chrono::Utc::now(),
168 )
169 .await
170 {
171 Ok(entry) => {
172 staged.commit().await;
173 HttpResponse::Ok().json(to_view(entry, &state.service_manager).await)
174 }
175 Err(error) => {
176 staged.rollback().await;
177 installer
180 .restart_services_after_failed_upgrade(&path_id, &stopped_services)
181 .await;
182 plugin_error_response(&error)
183 }
184 }
185}
186
187pub async fn remove_plugin(state: web::Data<AppState>, path: web::Path<String>) -> impl Responder {
189 let id = path.into_inner();
190 let installer = ServerPluginInstaller::new(state.clone());
191 match installer.uninstall(&id).await {
192 Ok(()) => HttpResponse::Ok().json(serde_json::json!({ "id": id, "removed": true })),
193 Err(error) => plugin_error_response(&error),
194 }
195}