symphony/
lib.rs

1/*
2 * Copyright (c) Microsoft Corporation.
3 * Licensed under the MIT license.
4 * SPDX-License-Identifier: MIT
5 */
6
7 use std::ffi::{CStr, CString};
8 use std::os::raw::c_char;
9 use libloading::{Library, Symbol};
10 use std::ptr;
11 use std::fs::File;
12 use std::io::{self, Read};
13 use sha2::{Sha256, Digest};
14 use std::collections::HashMap; 
15 
16 pub mod models;
17 use crate::models::*;
18 
19 // Function to compute the SHA-256 hash of a file
20 fn compute_sha256_hash(file_path: &str) -> Result<String, io::Error> {
21     let mut file = File::open(file_path)?;
22     let mut hasher = Sha256::new();
23     let mut buffer = [0; 4096];
24     
25     loop {
26         let bytes_read = file.read(&mut buffer)?;
27         if bytes_read == 0 {
28             break;
29         }
30         hasher.update(&buffer[..bytes_read]);
31     }
32     
33     let hash = hasher.finalize();
34     Ok(format!("{:x}", hash))
35 }
36 
37 // Function to validate the computed hash against the expected hash value
38 fn validate_hash(file_path: &str, expected_hash: &str) -> Result<(), String> {
39     let computed_hash = compute_sha256_hash(file_path).map_err(|e| format!("Error computing hash: {}", e))?;
40     
41     if computed_hash == expected_hash {        
42         Ok(())
43     } else {
44         Err(format!("Hash mismatch! Expected: {}, Got: {}", expected_hash, computed_hash))
45     }
46 }
47 
48 // Trait that all Rust-based Target providers must implement
49 pub trait ITargetProvider: Send + Sync {
50     fn init(&self, config: ProviderConfig) -> Result<(), String>;
51     fn get_validation_rule(&self) -> Result<ValidationRule, String>; // Return Rust native type
52     fn get(&self, deployment: DeploymentSpec, references: Vec<ComponentStep>) -> Result<Vec<ComponentSpec>, String>; // Return Rust native types
53     fn apply(&self, deployment: DeploymentSpec, step: DeploymentStep, is_dry_run: bool) -> Result<HashMap<String, ComponentResultSpec>, String>; 
54 }
55 
56 // Struct to hold the provider instance
57 #[repr(C)]
58 pub struct ProviderHandle {
59     provider: Box<dyn ITargetProvider>,
60     lib: Library, // Keep the library loaded to ensure the provider's functions remain valid
61 }
62 
63 pub struct ProviderWrapper {
64     pub inner: Box<dyn ITargetProvider>,
65 }
66 
67 // External function to create the provider instance
68 #[no_mangle]
69 pub extern "C" fn create_provider_instance(
70     provider_path: *const c_char, 
71     expected_hash: *const c_char
72 ) -> *mut ProviderHandle {
73     let provider_path = unsafe { CStr::from_ptr(provider_path) }
74         .to_str()
75         .expect("Invalid provider path");
76 
77     let expected_hash = unsafe { CStr::from_ptr(expected_hash) }
78         .to_str()
79         .expect("Invalid hash value");
80 
81     // Validate the hash before loading the provider
82     if let Err(err) = validate_hash(provider_path, expected_hash) {
83         eprintln!("Hash validation failed: {}", err);
84         return ptr::null_mut();
85     }
86 
87     let lib = unsafe { Library::new(provider_path).expect("Failed to load provider library") };
88 
89     type CreateProviderFn = unsafe fn() -> *mut ProviderWrapper;
90     let create_provider: Symbol<CreateProviderFn> = unsafe {
91         lib.get(b"create_provider\0").expect("Failed to load create_provider function")
92     };
93 
94     let wrapper = unsafe { create_provider() };
95 
96     if wrapper.is_null() {
97         eprintln!("Error: create_provider returned null pointer");
98         return ptr::null_mut();
99     }
100 
101     // Take ownership of the `Box<dyn ITargetProvider>` from the wrapper
102     let provider_box = unsafe { Box::from_raw(wrapper).inner };
103 
104     let handle = Box::new(ProviderHandle {
105         provider: provider_box, // Move the Box into ProviderHandle
106         lib: lib,
107     });
108 
109     Box::into_raw(handle)
110 }
111 
112 // Destroy the provider instance
113 #[no_mangle]
114 pub extern "C" fn destroy_provider_instance(handle: *mut ProviderHandle) {
115     if !handle.is_null() {
116         unsafe {
117             drop(Box::from_raw(handle));
118         }
119     }
120 }
121 
122 // Initialize the provider with JSON configuration
123 #[no_mangle]
124 pub extern "C" fn init_provider(handle: *mut ProviderHandle, config_json: *const c_char) -> i32 {    
125     if handle.is_null() {
126         eprintln!("Error: handle pointer is null");
127         return -1;
128     }
129     
130     if config_json.is_null() {
131         eprintln!("Error: config_json pointer is null");
132         return -1;
133     }
134     
135     let config_str = match unsafe { CStr::from_ptr(config_json).to_str() } {
136         Ok(str) => str,
137         Err(err) => {
138             eprintln!("Error converting config_json to string: {:?}", err);
139             return -1;
140         },
141     };
142     let config: ProviderConfig = match serde_json::from_str(config_str) {
143         Ok(cfg) => cfg,
144         Err(err) => {
145             eprintln!("Error deserializing config_json: {:?}", err);
146             return -1;
147         },
148     };
149     let handle_ref = unsafe { &*handle };
150     match handle_ref.provider.init(config) {
151         Ok(_) => {
152             return 0;
153         }
154         Err(err) => {
155             eprintln!("Error during provider initialization: {:?}", err);
156             return -1;
157         },
158     }
159 }
160 
161 // Get validation rule as a JSON string
162 #[no_mangle]
163 pub extern "C" fn get_validation_rule(handle: *mut ProviderHandle) -> *mut c_char {
164     let handle = unsafe { &*handle };
165     match handle.provider.get_validation_rule() {
166         Ok(validation_rule) => {
167             match CString::new(serde_json::to_string(&validation_rule).unwrap()) {
168                 Ok(cstr) => cstr.into_raw(),
169                 Err(_) => ptr::null_mut(),
170             }
171         }
172         Err(_) => ptr::null_mut(),
173     }
174 }
175 
176 // Get components as a JSON string
177 #[no_mangle]
178 pub extern "C" fn get(
179     handle: *mut ProviderHandle,
180     deployment_json: *const c_char,
181     references_json: *const c_char,
182 ) -> *mut c_char {
183     let handle = unsafe { &*handle };
184     let deployment_str = unsafe { CStr::from_ptr(deployment_json).to_str().unwrap() };
185     let references_str = unsafe { CStr::from_ptr(references_json).to_str().unwrap() };
186     let deployment: DeploymentSpec = serde_json::from_str(deployment_str).unwrap();
187     let references: Vec<ComponentStep> = serde_json::from_str(references_str).unwrap();
188     match handle.provider.get(deployment, references) {
189         Ok(components) => {
190             let json = serde_json::to_string(&components).unwrap();
191             CString::new(json).unwrap().into_raw()
192         }
193         Err(_) => {
194             println!("Error getting components");
195             ptr::null_mut()
196         } 
197     }
198 }
199 
200 // Apply deployment as a JSON string
201 #[no_mangle]
202 pub extern "C" fn apply(
203     handle: *mut ProviderHandle,
204     deployment_json: *const c_char,
205     step_json: *const c_char,
206     is_dry_run: i32,
207 ) -> *mut c_char {
208     let handle = unsafe { &*handle };
209     let deployment_str = unsafe { CStr::from_ptr(deployment_json).to_str().unwrap() };
210     let step_str = unsafe { CStr::from_ptr(step_json).to_str().unwrap() };
211     let deployment: DeploymentSpec = serde_json::from_str(deployment_str).unwrap();
212     let step: DeploymentStep = serde_json::from_str(step_str).unwrap();
213     let is_dry_run = is_dry_run != 0;
214    
215     match handle.provider.apply(deployment, step, is_dry_run) {
216         Ok(result_map) => {
217             let json = serde_json::to_string(&result_map).unwrap();
218             CString::new(json).unwrap().into_raw()
219         }
220         Err(_) => ptr::null_mut(),
221     }
222 }