mashin_sdk/lib.rs
1/* -------------------------------------------------------- *\
2 * *
3 * ███╗░░░███╗░█████╗░░██████╗██╗░░██╗██╗███╗░░██╗ *
4 * ████╗░████║██╔══██╗██╔════╝██║░░██║██║████╗░██║ *
5 * ██╔████╔██║███████║╚█████╗░███████║██║██╔██╗██║ *
6 * ██║╚██╔╝██║██╔══██║░╚═══██╗██╔══██║██║██║╚████║ *
7 * ██║░╚═╝░██║██║░░██║██████╔╝██║░░██║██║██║░╚███║ *
8 * ╚═╝░░░░░╚═╝╚═╝░░╚═╝╚═════╝░╚═╝░░╚═╝╚═╝╚═╝░░╚══╝ *
9 * by Nutshimit *
10 * -------------------------------------------------------- *
11 * *
12 * This file is licensed as MIT. See LICENSE for details. *
13 * *
14\* ---------------------------------------------------------*/
15
16//! The Mashin SDK is a library designed to facilitate the management of resources and providers
17//! within the Mashin engine for infrastructure-as-code solutions. It provides a powerful and
18//! flexible way to define, create, update, and delete resources across various cloud services,
19//! streamlining the process of creating and managing infrastructure components.
20//!
21//! The `construct_provider!` and `resource` macros are essential for developers to create custom
22//! providers and resources, which can then be exposed to the Mashin engine, enabling Ops teams to
23//! efficiently manage infrastructure components.
24//!
25//! - **`construct_provider!` macro**: This macro simplifies the process of creating custom
26//! providers by generating the necessary boilerplate code for implementing the `Provider` trait.
27//! Users only need to provide the provider-specific configuration and logic for handling
28//! resources.
29//!
30//! - **`resource` macro**: This macro generates the required code to implement the `Resource` trait
31//! for custom resources. Users only need to define the resource's properties and implement the
32//! logic for creating, updating, and deleting the resource using the provider.
33//!
34//!
35//! # Key concepts
36//!
37//! - **Provider**: A struct that represents a cloud service, such as AWS, Azure, or GCP, and
38//! implements the logic for creating, updating, and deleting resources on that service.
39//!
40//! - **Resource**: A struct that represents an individual infrastructure component, such as a
41//! virtual machine, a network, or a database.
42//!
43//! # Re-exports
44//!
45//! This module re-exports some helpers from other libraries, such as `serde`, `async_trait`,
46//! `parking_lot`, `serde_json`, and `tokio`. These re-exports are available under the `ext`
47//! submodule.
48//!
49//! # Example
50//! ```no_run
51//! mashin_sdk::construct_provider!(
52//! test_provider,
53//! resources = [my_resource],
54//! );
55//!
56//! #[mashin_sdk::resource]
57//! pub mod my_resource {
58//! #[mashin::config]
59//! pub struct Config {}
60//!
61//! #[mashin::resource]
62//! pub struct Resource {}
63//!
64//! #[mashin::calls]
65//! impl mashin_sdk::Resource for Resource { ... }
66//! }
67
68pub use crate::urn::Urn;
69pub use anyhow::Result;
70use async_trait::async_trait;
71pub use build::build;
72pub use deserialize::deserialize_state_field;
73pub use logger::CliLogger;
74pub use mashin_macro::{provider, resource};
75use parking_lot::Mutex;
76pub use provider_state::ProviderState;
77use serde::{Deserialize, Serialize};
78use serde_json::{json, Value};
79use std::{any::Any, cell::RefCell, fmt::Debug, rc::Rc, sync::Arc};
80
81mod build;
82mod deserialize;
83mod logger;
84mod provider;
85mod provider_state;
86mod urn;
87
88pub const KEY_CONFIG: &str = "__config";
89pub const KEY_URN: &str = "__urn";
90pub const KEY_NAME: &str = "__name";
91pub const KEY_SENSITIVE: &str = "__sensitive";
92// keys to skip
93pub const KEYS_CORE: [&str; 1] = [KEY_SENSITIVE];
94pub const KEY_VALUE: &str = "__value";
95
96/// Re-exports some helpers from other libraries
97pub mod ext {
98 pub use anyhow;
99 pub use async_trait;
100 pub use parking_lot;
101 pub use serde;
102 pub use serde_json;
103 pub use tokio;
104}
105
106/// Unique resource id within a provider
107pub type ResourceId = u32;
108
109/// A struct that holds the input arguments for resource actions, such as the resource's URN,
110/// the raw configuration, and the raw state.
111#[derive(Debug, Serialize, Deserialize)]
112pub struct ResourceArgs {
113 pub action: Rc<ResourceAction>,
114 pub urn: Rc<Urn>,
115 pub raw_config: Rc<Value>,
116 pub raw_state: Rc<RefCell<Value>>,
117}
118
119/// An enum that defines the possible actions that can be performed on a
120/// resource, such as creating, updating, or deleting
121#[derive(Default, Clone, Debug, PartialEq, Serialize, Deserialize)]
122pub enum ResourceAction {
123 Update {
124 diff: Rc<ResourceDiff>,
125 },
126 Create,
127 Delete,
128 #[default]
129 Get,
130}
131
132impl ResourceAction {
133 /// Present participe of the action
134 pub fn action_present_participe_str(&self) -> &str {
135 match self {
136 ResourceAction::Update { .. } => "Updating",
137 ResourceAction::Create => "Creating",
138 ResourceAction::Delete => "Deleting",
139 ResourceAction::Get => "Reading",
140 }
141 }
142 /// Simple present of the action
143 pub fn action_present_str(&self) -> &str {
144 match self {
145 ResourceAction::Update { .. } => "Update",
146 ResourceAction::Create => "Create",
147 ResourceAction::Delete => "Delete",
148 ResourceAction::Get => "Read",
149 }
150 }
151
152 pub fn action_past_str(&self) -> &str {
153 match self {
154 ResourceAction::Update { .. } => "Updated",
155 ResourceAction::Create => "Created",
156 ResourceAction::Delete => "Deleted",
157 ResourceAction::Get => "Read",
158 }
159 }
160}
161
162/// A struct that holds information about the differences between two resource states,
163/// such as the properties that have changed during an update operation.
164#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
165pub struct ResourceDiff(Vec<String>);
166
167impl ResourceDiff {
168 pub fn new(diff: Vec<String>) -> Self {
169 Self(diff)
170 }
171
172 pub fn has_change(&self, key: impl ToString) -> bool {
173 self.0.contains(&key.to_string())
174 }
175}
176
177/// `ResourceResult` represents the serialized state of a resource after it has been processed
178/// by a provider. The Mashin engine uses this data to compare the actual state with the desired
179/// state, determining whether any changes have occurred.
180///
181/// When updating a resource, `ResourceResult` should also include any changes to the resource's
182#[derive(Debug, Clone, Serialize, Deserialize)]
183pub struct ResourceResult(serde_json::Value);
184
185impl ResourceResult {
186 pub fn new(raw_state_as_json: serde_json::Value) -> Self {
187 ResourceResult(raw_state_as_json)
188 }
189
190 pub fn inner(&self) -> serde_json::Value {
191 self.0.clone()
192 }
193}
194
195/// A trait for resource equality comparisons.
196pub trait ResourceEq {
197 /// Returns the resource as a `&dyn Any`.
198 fn as_any(&self) -> &dyn Any;
199 /// Compares two resources for equality.
200 ///
201 /// Returns `true` if the resources are equal, `false` otherwise.
202 fn is_eq(&self, other: &dyn Resource) -> bool;
203}
204
205/// A trait for serializing a resource to its raw state.
206pub trait ResourceSerialize {
207 /// Converts the resource to its raw state as a `serde_json::Value`.
208 fn to_raw_state(&self) -> Result<serde_json::Value>;
209}
210
211/// A trait representing default behavior for a resource.
212pub trait ResourceDefault {
213 fn new(name: &str, urn: &str) -> Self
214 where
215 Self: Sized;
216 fn set_raw_config(&mut self, config: &Rc<Value>);
217 fn from_current_state(
218 name: &str,
219 urn: &str,
220 raw_state: Rc<RefCell<Value>>,
221 ) -> Result<Rc<RefCell<Self>>>
222 where
223 Self: Default,
224 for<'de> Self: Deserialize<'de>,
225 {
226 let state = raw_state.borrow_mut();
227 if state.as_null().is_some() {
228 Ok(Rc::new(RefCell::new(Self::new(name, urn))))
229 } else {
230 let mut state = state.clone();
231 let merge_fields = json!({
232 "__name": {
233 "value": name,
234 "sensitive": true,
235 },
236 "__urn": {
237 "value": urn,
238 "sensitive": true,
239 },
240 });
241
242 merge_json(&mut state, &merge_fields);
243
244 Ok(Rc::new(RefCell::new(::serde_json::from_value::<Self>(state)?)))
245 }
246 }
247 /// Returns the name of the resource.
248 fn name(&self) -> &str;
249 /// Returns the URN of the resource.
250 fn urn(&self) -> &str;
251}
252
253/// A trait representing a resource in the Mashin SDK.
254///
255/// A resource is a manageable entity within a provider, such as a virtual
256/// machine, database, or storage container. This trait defines the methods
257/// necessary for managing the resource's lifecycle.
258///
259/// The Resource state is generated from the `self` value.
260#[async_trait]
261pub trait Resource: ResourceEq + ResourceSerialize + ResourceDefault {
262 /// Retrieves the current state of the resource.
263 ///
264 /// ### Arguments
265 ///
266 /// * `provider_state` - An `Arc<Mutex<ProviderState>>` that represents the current state of the provider.
267 ///
268 /// ### Returns
269 ///
270 /// A `Result` that indicates whether the operation was successful or not.
271 async fn get(&mut self, provider_state: Arc<Mutex<ProviderState>>) -> Result<()>;
272 /// Creates the resource.
273 ///
274 /// ### Arguments
275 ///
276 /// * `provider_state` - An `Arc<Mutex<ProviderState>>` that represents the current state of the provider.
277 ///
278 /// ### Returns
279 ///
280 /// A `Result` that indicates whether the operation was successful or not.
281 async fn create(&mut self, provider_state: Arc<Mutex<ProviderState>>) -> Result<()>;
282 /// Deletes the resource.
283 ///
284 /// ### Arguments
285 ///
286 /// * `provider_state` - An `Arc<Mutex<ProviderState>>` that represents the current state of the provider.
287 ///
288 /// ### Returns
289 ///
290 /// A `Result` that indicates whether the operation was successful or not.
291 async fn delete(&mut self, provider_state: Arc<Mutex<ProviderState>>) -> Result<()>;
292 /// Updates the resource with new data.
293 ///
294 /// # Arguments
295 ///
296 /// * `provider_state` - An `Arc<Mutex<ProviderState>>` that represents the current state of the provider.
297 /// * `diff` - A `ResourceDiff` that represents the changes to be applied to the resource.
298 ///
299 /// # Returns
300 ///
301 /// A `Result` that indicates whether the operation was successful or not.
302 async fn update(
303 &mut self,
304 provider_state: Arc<Mutex<ProviderState>>,
305 diff: &ResourceDiff,
306 ) -> Result<()>;
307}
308
309impl<R: 'static + PartialEq> ResourceEq for R {
310 fn as_any(&self) -> &dyn Any {
311 self
312 }
313
314 fn is_eq(&self, other: &dyn Resource) -> bool {
315 // Do a type-safe casting. If the types are different,
316 // return false, otherwise test the values for equality.
317 other.as_any().downcast_ref::<R>().map_or(false, |a| self == a)
318 }
319}
320
321impl<R: Serialize> ResourceSerialize for R {
322 fn to_raw_state(&self) -> Result<serde_json::Value> {
323 serde_json::to_value(self).map_err(Into::into)
324 }
325}
326
327/// A trait representing a builder for a provider, which is responsible for
328/// initializing the provider and setting up the initial state.
329#[async_trait]
330pub trait ProviderBuilder {
331 /// Asynchronously builds the provider.
332 ///
333 /// This method is called when the provider is being initialized and should
334 /// contain any logic necessary for setting up the provider.
335 async fn build(&mut self) -> Result<()>;
336}
337
338/// A trait representing default behavior for a provider.
339/// This is implemented automatically by the macros.
340pub trait ProviderDefault {
341 /// Returns the current state of the provider as an `Arc<Mutex<ProviderState>>`.
342 ///
343 /// The state can be used to pass data between the provider and its resources.
344 fn state(&mut self) -> Arc<Mutex<ProviderState>>;
345 /// Builds a dynamic resource from the given URN and raw state.
346 ///
347 /// This method is responsible for matching the URN and applying the current
348 /// JSON value from the state to the correct resource.
349 fn build_resource(
350 &self,
351 urn: &Rc<Urn>,
352 state: &Rc<RefCell<Value>>,
353 ) -> Result<Rc<RefCell<dyn Resource>>>;
354}
355
356/// A trait representing a provider in the Mashin SDK.
357///
358/// A provider is responsible for managing resources and their lifecycle.
359#[async_trait]
360pub trait Provider: ProviderBuilder + ProviderDefault {}
361
362/// Merges two JSON values, deeply combining them into a single JSON value.
363///
364/// If both input values are JSON objects, their key-value pairs are merged
365/// recursively. In case of a key collision, the value from `b` is used.
366/// For all other JSON value types, the value from `b` simply replaces the value in `a`.
367///
368/// ### Arguments
369///
370/// * `a` - The mutable reference to the first JSON value to be merged
371/// * `b` - The reference to the second JSON value to be merged
372///
373/// ### Examples
374///
375/// ```
376/// use serde_json::json;
377/// use mashin_sdk::merge_json;
378///
379/// let mut a = json!({
380/// "name": "Alice",
381/// "age": 30,
382/// "nested": {
383/// "a": 1,
384/// "b": 2
385/// }
386/// });
387///
388/// let b = json!({
389/// "age": 31,
390/// "city": "New York",
391/// "nested": {
392/// "b": 3,
393/// "c": 4
394/// }
395/// });
396///
397/// merge_json(&mut a, &b);
398///
399/// assert_eq!(a, json!({
400/// "name": "Alice",
401/// "age": 31,
402/// "city": "New York",
403/// "nested": {
404/// "a": 1,
405/// "b": 3,
406/// "c": 4
407/// }
408/// }));
409/// ```
410pub fn merge_json(a: &mut serde_json::Value, b: &serde_json::Value) {
411 match (a, b) {
412 (&mut serde_json::Value::Object(ref mut a), serde_json::Value::Object(b)) => {
413 for (k, v) in b {
414 merge_json(a.entry(k.clone()).or_insert(serde_json::Value::Null), v);
415 }
416 },
417 (a, b) => {
418 *a = b.clone();
419 },
420 }
421}