Skip to main content

edgehog_device_runtime_containers/requests/
deployment.rs

1// This file is part of Edgehog.
2//
3// Copyright 2024 SECO Mind Srl
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//   http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16//
17// SPDX-License-Identifier: Apache-2.0
18
19//! Create image request
20
21use astarte_device_sdk::{event::FromEventError, types::TypeError, AstarteData, FromEvent};
22use tracing::error;
23use uuid::Uuid;
24
25use super::{ReqUuid, VecReqUuid};
26
27/// Request to pull a Docker Deployment.
28#[derive(Debug, Clone, FromEvent, PartialEq, Eq, PartialOrd, Ord)]
29#[from_event(
30    interface = "io.edgehog.devicemanager.apps.CreateDeploymentRequest",
31    path = "/deployment",
32    rename_all = "camelCase",
33    aggregation = "object"
34)]
35pub struct CreateDeployment {
36    pub(crate) id: ReqUuid,
37    pub(crate) containers: VecReqUuid,
38}
39
40/// Command for a previously received deployment
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct DeploymentCommand {
43    pub(crate) id: Uuid,
44    pub(crate) command: CommandValue,
45}
46
47impl FromEvent for DeploymentCommand {
48    type Err = FromEventError;
49
50    fn from_event(event: astarte_device_sdk::DeviceEvent) -> Result<Self, Self::Err> {
51        let id = event
52            .path
53            .strip_prefix('/')
54            .and_then(|path| path.strip_suffix("/command"))
55            .and_then(|id| Uuid::parse_str(id).ok())
56            .ok_or_else(|| FromEventError::Path {
57                interface: "io.edgehog.devicemanager.apps.DeploymentCommand",
58                base_path: event.path.clone(),
59            })?;
60
61        let event = DeploymentCommandEvent::from_event(event)?;
62
63        match event {
64            DeploymentCommandEvent::Command(command) => Ok(Self { id, command }),
65        }
66    }
67}
68
69#[derive(Debug, Clone, FromEvent)]
70#[from_event(
71    interface = "io.edgehog.devicemanager.apps.DeploymentCommand",
72    aggregation = "individual"
73)]
74enum DeploymentCommandEvent {
75    #[mapping(endpoint = "/%{deployment_id}/command")]
76    Command(CommandValue),
77}
78
79#[derive(Debug, Clone, Copy, PartialEq, Eq)]
80pub(crate) enum CommandValue {
81    Start,
82    Stop,
83    Delete,
84}
85
86impl TryFrom<AstarteData> for CommandValue {
87    type Error = TypeError;
88
89    fn try_from(value: AstarteData) -> Result<Self, Self::Error> {
90        let value = String::try_from(value)?;
91
92        match value.as_str() {
93            "Start" => Ok(Self::Start),
94            "Stop" => Ok(Self::Stop),
95            "Delete" => Ok(Self::Delete),
96            _ => {
97                error!("unrecognize DeploymentCommand command value {value}");
98
99                Err(TypeError::Conversion {
100                    ctx: format!("unrecognize DeploymentCommand command value {value}"),
101                })
102            }
103        }
104    }
105}
106
107/// Request to update between two deployments.
108#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
109pub struct DeploymentUpdate {
110    pub(crate) from: Uuid,
111    pub(crate) to: Uuid,
112}
113
114impl FromEvent for DeploymentUpdate {
115    type Err = FromEventError;
116
117    fn from_event(event: astarte_device_sdk::DeviceEvent) -> Result<Self, Self::Err> {
118        let event = DeploymentUpdateEvent::from_event(event)?;
119
120        let from = Uuid::parse_str(&event.from).map_err(|err| {
121            error!(
122                error = format!("{:#}", eyre::Report::new(err)),
123                from = event.from,
124                "invalid deployment update 'from' uuid"
125            );
126
127            FromEventError::Conversion(TypeError::Conversion {
128                ctx: "invalid deployment update 'from' uuid".to_string(),
129            })
130        })?;
131
132        let to = Uuid::parse_str(&event.to).map_err(|err| {
133            error!(
134                error = format!("{:#}", eyre::Report::new(err)),
135                to = event.to,
136                "invalid deployment update 'to' uuid"
137            );
138
139            FromEventError::Conversion(TypeError::Conversion {
140                ctx: "invalid deployment update 'to' uuid".to_string(),
141            })
142        })?;
143
144        Ok(Self { from, to })
145    }
146}
147
148#[derive(Debug, Clone, FromEvent, PartialEq, Eq, PartialOrd, Ord)]
149#[from_event(
150    interface = "io.edgehog.devicemanager.apps.DeploymentUpdate",
151    path = "/deployment",
152    rename_all = "camelCase",
153    aggregation = "object"
154)]
155struct DeploymentUpdateEvent {
156    from: String,
157    to: String,
158}
159
160#[cfg(test)]
161pub(crate) mod tests {
162
163    use std::fmt::Display;
164
165    use astarte_device_sdk::aggregate::AstarteObject;
166    use astarte_device_sdk::chrono::Utc;
167    use astarte_device_sdk::{AstarteData, DeviceEvent, Value};
168    use pretty_assertions::assert_eq;
169
170    use super::*;
171
172    pub fn create_deployment_request_event<S: Display>(id: &str, containers: &[S]) -> DeviceEvent {
173        let fields = [
174            ("id", AstarteData::String(id.to_string())),
175            (
176                "containers",
177                AstarteData::StringArray(containers.iter().map(|s| s.to_string()).collect()),
178            ),
179        ]
180        .into_iter()
181        .map(|(k, v)| (k.to_string(), v))
182        .collect();
183
184        DeviceEvent {
185            interface: "io.edgehog.devicemanager.apps.CreateDeploymentRequest".to_string(),
186            path: "/deployment".to_string(),
187            data: Value::Object {
188                data: fields,
189                timestamp: Utc::now(),
190            },
191        }
192    }
193
194    #[test]
195    fn create_deployment_request() {
196        let id = ReqUuid(Uuid::new_v4());
197        let containers = VecReqUuid(vec![ReqUuid(Uuid::new_v4())]);
198        let event = create_deployment_request_event(&id.to_string(), &containers);
199
200        let request = CreateDeployment::from_event(event).unwrap();
201
202        let expect = CreateDeployment { id, containers };
203
204        assert_eq!(request, expect);
205    }
206
207    #[test]
208    fn deployment_command() {
209        let id = Uuid::new_v4();
210
211        let event = DeviceEvent {
212            interface: "io.edgehog.devicemanager.apps.DeploymentCommand".to_string(),
213            path: format!("/{id}/command"),
214            data: Value::Individual {
215                data: "Start".into(),
216                timestamp: Utc::now(),
217            },
218        };
219
220        let exp = DeploymentCommand {
221            id,
222            command: CommandValue::Start,
223        };
224
225        let cmd = DeploymentCommand::from_event(event).unwrap();
226        assert_eq!(cmd, exp);
227
228        let event = DeviceEvent {
229            interface: "io.edgehog.devicemanager.apps.DeploymentCommand".to_string(),
230            path: format!("/{id}/command"),
231            data: Value::Individual {
232                data: "Stop".into(),
233                timestamp: Utc::now(),
234            },
235        };
236
237        let exp = DeploymentCommand {
238            id,
239            command: CommandValue::Stop,
240        };
241
242        let cmd = DeploymentCommand::from_event(event).unwrap();
243        assert_eq!(cmd, exp);
244    }
245
246    #[test]
247    fn deployment_update() {
248        let from = Uuid::new_v4();
249        let to = Uuid::new_v4();
250
251        let data = AstarteObject::from_iter([
252            ("from".to_string(), AstarteData::String(from.to_string())),
253            ("to".to_string(), AstarteData::String(to.to_string())),
254        ]);
255
256        let event = DeviceEvent {
257            interface: "io.edgehog.devicemanager.apps.DeploymentUpdate".to_string(),
258            path: "/deployment".to_string(),
259            data: Value::Object {
260                data,
261                timestamp: Utc::now(),
262            },
263        };
264
265        let exp = DeploymentUpdate { from, to };
266
267        let cmd = DeploymentUpdate::from_event(event).unwrap();
268        assert_eq!(cmd, exp);
269    }
270}