1use std::collections::HashSet;
4
5use tokio_util::sync::CancellationToken;
6
7use crate::config::Service;
8use crate::daemon::NodeOptions;
9use crate::mirror::RestorePoint;
10use crate::response::{
11 MirrorRestoreResponse, MirrorSyncResponse, RegisterResponse, StatusResponse,
12};
13use crate::root::FleetRoot;
14use crate::{daemon, ops};
15
16#[derive(Clone)]
19pub struct Fleet {
20 root: FleetRoot,
21}
22
23impl Fleet {
24 pub fn open(url: &str) -> Result<Self, Error> {
27 Ok(Self {
28 root: FleetRoot::open(url)?,
29 })
30 }
31
32 #[doc(hidden)]
35 pub fn from_root(root: FleetRoot) -> Self {
36 Self { root }
37 }
38
39 pub fn url(&self) -> &str {
41 self.root.url()
42 }
43
44 pub async fn register(&self, database_url: &str) -> Result<RegisterResponse, Error> {
46 Ok(ops::register(&self.root, database_url).await?)
47 }
48
49 pub async fn status(&self, options: StatusOptions) -> Result<StatusResponse, Error> {
51 Ok(ops::status(&self.root, options.compactions, options.mirrors).await?)
52 }
53
54 pub async fn sync_mirror(
56 &self,
57 database_url: &str,
58 target: &str,
59 options: MirrorSyncOptions,
60 ) -> Result<MirrorSyncResponse, Error> {
61 Ok(ops::mirror_sync(&self.root, database_url, target, options.rclone.as_deref()).await?)
62 }
63
64 pub async fn run_node(
67 &self,
68 options: NodeOptions,
69 shutdown: CancellationToken,
70 ) -> Result<(), Error> {
71 let mut seen = HashSet::new();
72 if let Some(duplicate) = options
73 .services
74 .iter()
75 .find(|service| !seen.insert(**service))
76 {
77 return Err(Error::DuplicateService {
78 service: *duplicate,
79 });
80 }
81 daemon::run(self.root.clone(), options, shutdown).await?;
82 Ok(())
83 }
84}
85
86pub async fn mirror_restore(
88 backup_url: &str,
89 destination_url: &str,
90 point: RestorePoint,
91) -> Result<MirrorRestoreResponse, Error> {
92 Ok(ops::mirror_restore(backup_url, destination_url, point).await?)
93}
94
95#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
97pub struct StatusOptions {
98 compactions: bool,
99 mirrors: bool,
100}
101
102impl StatusOptions {
103 pub fn with_compactions(mut self, enabled: bool) -> Self {
105 self.compactions = enabled;
106 self
107 }
108
109 pub fn with_mirrors(mut self, enabled: bool) -> Self {
111 self.mirrors = enabled;
112 self
113 }
114}
115
116#[derive(Clone, Debug, Default, PartialEq, Eq)]
118pub struct MirrorSyncOptions {
119 rclone: Option<String>,
120}
121
122impl MirrorSyncOptions {
123 pub fn with_rclone(mut self, rclone: impl Into<String>) -> Self {
126 self.rclone = Some(rclone.into());
127 self
128 }
129}
130
131#[derive(Debug, thiserror::Error)]
133#[non_exhaustive]
134pub enum Error {
135 #[error("invalid fleet root: {0}")]
137 FleetRootUrl(#[source] crate::registry::UrlError),
138 #[error("failed to open fleet root store: {0}")]
140 FleetRootStore(#[source] object_store::Error),
141 #[error(transparent)]
143 Url(#[from] crate::registry::UrlError),
144 #[error("object store error: {0}")]
146 Store(#[from] object_store::Error),
147 #[error("{url} is not registered; `sleet register` it first")]
149 NotRegistered {
150 url: String,
152 },
153 #[error("no enabled mirror target {target:?} applies to {url}")]
155 NoSuchMirrorTarget {
156 target: String,
158 url: String,
160 },
161 #[error(transparent)]
163 Service(#[from] crate::services::ServiceError),
164 #[error(transparent)]
166 Mirror(#[from] crate::mirror::MirrorError),
167 #[error("invalid node id: {0}")]
169 InvalidNodeId(String),
170 #[error("services lists {name:?} more than once", name = service.as_str())]
172 DuplicateService {
173 service: Service,
175 },
176}
177
178impl From<crate::root::OpenError> for Error {
179 fn from(error: crate::root::OpenError) -> Self {
180 match error {
181 crate::root::OpenError::Url(source) => Self::FleetRootUrl(source),
182 crate::root::OpenError::Store(source) => Self::FleetRootStore(source),
183 }
184 }
185}
186
187impl From<ops::OpsError> for Error {
188 fn from(error: ops::OpsError) -> Self {
189 match error {
190 ops::OpsError::Url(source) => Self::Url(source),
191 ops::OpsError::Store(source) => Self::Store(source),
192 ops::OpsError::NotRegistered { url } => Self::NotRegistered { url },
193 ops::OpsError::NoSuchTarget { target, url } => Self::NoSuchMirrorTarget { target, url },
194 ops::OpsError::Service(source) => Self::Service(source),
195 ops::OpsError::Mirror(source) => Self::Mirror(source),
196 }
197 }
198}
199
200impl From<daemon::DaemonError> for Error {
201 fn from(error: daemon::DaemonError) -> Self {
202 match error {
203 daemon::DaemonError::NodeId(reason) => Self::InvalidNodeId(reason),
204 daemon::DaemonError::Root(source) => source.into(),
205 }
206 }
207}