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}