1use crate::error::RuntimeError;
9use crate::loaded::ExtensionId;
10use crate::runtime::ExtensionRuntime;
11use crate::types::{DeployExtensionError, DeployJob, DeployRequest, DeployStatus};
12
13use crate::host_bindings::deploy::exports::greentic::extension_deploy0_1_0::deployment::{
14 DeployJob as WitDeployJob, DeployRequest as WitDeployRequest, DeployStatus as WitDeployStatus,
15};
16use crate::host_bindings::deploy::greentic::extension_base0_1_0::types::ExtensionError as WitExtensionError;
17use crate::host_bindings::deploy_v02::exports::greentic::extension_deploy0_2_0::deployment::{
18 DeployJob as WitDeployJobV2, DeployRequest as WitDeployRequestV2,
19 DeployStatus as WitDeployStatusV2,
20};
21use crate::host_bindings::deploy_v02::greentic::extension_base0_2_0::types::ExtensionError as WitExtensionErrorV2;
22
23const BASE_IFACE: &str = "greentic:extension-deploy/deployment";
24
25impl ExtensionRuntime {
26 pub fn deploy(&self, ext_id: &str, req: DeployRequest) -> Result<DeployJob, RuntimeError> {
35 let (mut store, instance) = self.deploy_instance(ext_id)?;
36 let (iface_idx, version) = resolve_deployment_iface(&mut store, &instance)?;
37 let func_idx = resolve_func(&mut store, &instance, &iface_idx, "deploy")?;
38 if version == "0.2.0" {
39 let func = instance
40 .get_typed_func::<(WitDeployRequestV2,), (Result<WitDeployJobV2, WitExtensionErrorV2>,)>(
41 &mut store, &func_idx,
42 )
43 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
44 let wit_req = WitDeployRequestV2 {
45 target_id: req.target_id,
46 artifact_bytes: req.artifact_bytes,
47 credentials_json: req.credentials_json,
48 config_json: req.config_json,
49 deployment_name: req.deployment_name,
50 };
51 let (result,) = func
52 .call(&mut store, (wit_req,))
53 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
54 result
55 .map(job_to_host_v2)
56 .map_err(|e| RuntimeError::Deploy(err_to_host_v2(e)))
57 } else {
58 let func = instance
59 .get_typed_func::<(WitDeployRequest,), (Result<WitDeployJob, WitExtensionError>,)>(
60 &mut store, &func_idx,
61 )
62 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
63 let wit_req = WitDeployRequest {
64 target_id: req.target_id,
65 artifact_bytes: req.artifact_bytes,
66 credentials_json: req.credentials_json,
67 config_json: req.config_json,
68 deployment_name: req.deployment_name,
69 };
70 let (result,) = func
71 .call(&mut store, (wit_req,))
72 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
73 result
74 .map(job_to_host)
75 .map_err(|e| RuntimeError::Deploy(err_to_host(e)))
76 }
77 }
78
79 pub fn deploy_poll(&self, ext_id: &str, job_id: &str) -> Result<DeployJob, RuntimeError> {
81 let (mut store, instance) = self.deploy_instance(ext_id)?;
82 let (iface_idx, version) = resolve_deployment_iface(&mut store, &instance)?;
83 let func_idx = resolve_func(&mut store, &instance, &iface_idx, "poll")?;
84 if version == "0.2.0" {
85 let func = instance
86 .get_typed_func::<(String,), (Result<WitDeployJobV2, WitExtensionErrorV2>,)>(
87 &mut store, &func_idx,
88 )
89 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
90 let (result,) = func
91 .call(&mut store, (job_id.to_string(),))
92 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
93 result
94 .map(job_to_host_v2)
95 .map_err(|e| RuntimeError::Deploy(err_to_host_v2(e)))
96 } else {
97 let func = instance
98 .get_typed_func::<(String,), (Result<WitDeployJob, WitExtensionError>,)>(
99 &mut store, &func_idx,
100 )
101 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
102 let (result,) = func
103 .call(&mut store, (job_id.to_string(),))
104 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
105 result
106 .map(job_to_host)
107 .map_err(|e| RuntimeError::Deploy(err_to_host(e)))
108 }
109 }
110
111 pub fn deploy_rollback(&self, ext_id: &str, job_id: &str) -> Result<(), RuntimeError> {
113 let (mut store, instance) = self.deploy_instance(ext_id)?;
114 let (iface_idx, version) = resolve_deployment_iface(&mut store, &instance)?;
115 let func_idx = resolve_func(&mut store, &instance, &iface_idx, "rollback")?;
116 if version == "0.2.0" {
117 let func = instance
118 .get_typed_func::<(String,), (Result<(), WitExtensionErrorV2>,)>(
119 &mut store, &func_idx,
120 )
121 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
122 let (result,) = func
123 .call(&mut store, (job_id.to_string(),))
124 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
125 result.map_err(|e| RuntimeError::Deploy(err_to_host_v2(e)))
126 } else {
127 let func = instance
128 .get_typed_func::<(String,), (Result<(), WitExtensionError>,)>(
129 &mut store, &func_idx,
130 )
131 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
132 let (result,) = func
133 .call(&mut store, (job_id.to_string(),))
134 .map_err(|e| RuntimeError::Wasmtime(e.into()))?;
135 result.map_err(|e| RuntimeError::Deploy(err_to_host(e)))
136 }
137 }
138
139 fn deploy_instance(
146 &self,
147 ext_id: &str,
148 ) -> Result<
149 (
150 wasmtime::Store<crate::host_state::HostState>,
151 wasmtime::component::Instance,
152 ),
153 RuntimeError,
154 > {
155 let loaded = self
156 .loaded()
157 .get(&ExtensionId(ext_id.to_string()))
158 .cloned()
159 .ok_or_else(|| RuntimeError::NotFound(ext_id.to_string()))?;
160 loaded
161 .build_store_and_instance(
162 self.engine(),
163 self.host_overrides().clone(),
164 &crate::host_ports::HostCallContext::default(),
165 )
166 .map_err(RuntimeError::Wasmtime)
167 }
168}
169
170fn resolve_deployment_iface(
174 store: &mut wasmtime::Store<crate::host_state::HostState>,
175 instance: &wasmtime::component::Instance,
176) -> Result<(wasmtime::component::ComponentExportIndex, &'static str), RuntimeError> {
177 let (iface_idx, _name, version) = crate::runtime::resolve_iface_versions(
178 store,
179 instance,
180 BASE_IFACE,
181 crate::runtime::DEPLOY_VERSIONS,
182 )?;
183 Ok((iface_idx, version))
184}
185
186fn resolve_func(
187 store: &mut wasmtime::Store<crate::host_state::HostState>,
188 instance: &wasmtime::component::Instance,
189 iface_idx: &wasmtime::component::ComponentExportIndex,
190 name: &str,
191) -> Result<wasmtime::component::ComponentExportIndex, RuntimeError> {
192 instance
193 .get_export_index(&mut *store, Some(iface_idx), name)
194 .ok_or_else(|| {
195 RuntimeError::Wasmtime(anyhow::anyhow!(
196 "interface '{BASE_IFACE}' does not export '{name}'"
197 ))
198 })
199}
200
201fn job_to_host(j: WitDeployJob) -> DeployJob {
202 DeployJob {
203 id: j.id,
204 status: match j.status {
205 WitDeployStatus::Pending => DeployStatus::Pending,
206 WitDeployStatus::Provisioning => DeployStatus::Provisioning,
207 WitDeployStatus::Configuring => DeployStatus::Configuring,
208 WitDeployStatus::Starting => DeployStatus::Starting,
209 WitDeployStatus::Running => DeployStatus::Running,
210 WitDeployStatus::Failed => DeployStatus::Failed,
211 WitDeployStatus::RolledBack => DeployStatus::RolledBack,
212 },
213 message: j.message,
214 endpoints: j.endpoints,
215 }
216}
217
218fn err_to_host(e: WitExtensionError) -> DeployExtensionError {
219 match e {
220 WitExtensionError::InvalidInput(m) => DeployExtensionError::InvalidInput(m),
221 WitExtensionError::MissingCapability(m) => DeployExtensionError::MissingCapability(m),
222 WitExtensionError::PermissionDenied(m) => DeployExtensionError::PermissionDenied(m),
223 WitExtensionError::Internal(m) => DeployExtensionError::Internal(m),
224 }
225}
226
227fn job_to_host_v2(j: WitDeployJobV2) -> DeployJob {
228 DeployJob {
229 id: j.id,
230 status: match j.status {
231 WitDeployStatusV2::Pending => DeployStatus::Pending,
232 WitDeployStatusV2::Provisioning => DeployStatus::Provisioning,
233 WitDeployStatusV2::Configuring => DeployStatus::Configuring,
234 WitDeployStatusV2::Starting => DeployStatus::Starting,
235 WitDeployStatusV2::Running => DeployStatus::Running,
236 WitDeployStatusV2::Failed => DeployStatus::Failed,
237 WitDeployStatusV2::RolledBack => DeployStatus::RolledBack,
238 },
239 message: j.message,
240 endpoints: j.endpoints,
241 }
242}
243
244fn err_to_host_v2(e: WitExtensionErrorV2) -> DeployExtensionError {
245 match e {
246 WitExtensionErrorV2::InvalidInput(m) => DeployExtensionError::InvalidInput(m),
247 WitExtensionErrorV2::MissingCapability(m) => DeployExtensionError::MissingCapability(m),
248 WitExtensionErrorV2::PermissionDenied(m) => DeployExtensionError::PermissionDenied(m),
249 WitExtensionErrorV2::NotFound(m) => DeployExtensionError::NotFound(m),
250 WitExtensionErrorV2::SchemaInvalid(m) => DeployExtensionError::SchemaInvalid(m),
251 WitExtensionErrorV2::Internal(m) => DeployExtensionError::Internal(m),
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258
259 #[test]
260 fn deploy_returns_not_found_for_unknown_extension() {
261 let rt = ExtensionRuntime::for_test();
262 let req = DeployRequest {
263 target_id: "github-repo".into(),
264 artifact_bytes: vec![1, 2, 3],
265 credentials_json: "{}".into(),
266 config_json: "{}".into(),
267 deployment_name: "demo".into(),
268 };
269 match rt.deploy("greentic.deploy-github", req) {
270 Err(RuntimeError::NotFound(id)) => assert_eq!(id, "greentic.deploy-github"),
271 other => panic!("expected NotFound, got {other:?}"),
272 }
273 }
274
275 #[test]
276 fn deploy_poll_returns_not_found_for_unknown_extension() {
277 let rt = ExtensionRuntime::for_test();
278 match rt.deploy_poll("greentic.deploy-github", "job-1") {
279 Err(RuntimeError::NotFound(id)) => assert_eq!(id, "greentic.deploy-github"),
280 other => panic!("expected NotFound, got {other:?}"),
281 }
282 }
283
284 #[test]
285 fn deploy_rollback_returns_not_found_for_unknown_extension() {
286 let rt = ExtensionRuntime::for_test();
287 match rt.deploy_rollback("greentic.deploy-github", "job-1") {
288 Err(RuntimeError::NotFound(id)) => assert_eq!(id, "greentic.deploy-github"),
289 other => panic!("expected NotFound, got {other:?}"),
290 }
291 }
292}