Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions docs/src/content/docs/guides/manage-databases.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,21 @@ command = {

4. **Reuse**: This final command is cached and reused for all SQL operations in that spawn session.

### Avoiding Repeated SSH Passphrase Prompts

When using SSH keys with a passphrase, each connection to the database will prompt for the passphrase. Since spawn opens multiple SSH sessions during a single `apply` run, this gets tedious quickly.

To avoid this, add your key to the SSH agent before running spawn:

```bash
# macOS
ssh-add ~/.ssh/your_key

# Linux
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/your_key
```

### Complete Example: Multiple Environments

```toml
Expand Down
10 changes: 7 additions & 3 deletions src/store/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,10 +125,14 @@ impl Store {
Ok(res)
}

pub async fn load_component_bytes(&self, name: &str) -> Result<Option<Vec<u8>>> {
self.pinner.load_bytes(name, &self.fs).await
}

pub async fn read_file_bytes(&self, path: &str) -> Result<Vec<u8>> {
let full_path = format!("{}/{}", self.pather.components_folder(), path);
let result = self.fs.read(&full_path).await?;
Ok(result.to_bytes().to_vec())
self.load_component_bytes(path)
.await?
.ok_or_else(|| anyhow::anyhow!("file not found in components: {}", path))
}

pub async fn load_migration(&self, name: &str) -> Result<String> {
Expand Down
6 changes: 2 additions & 4 deletions src/store/pinner/latest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,13 @@ impl Latest {
#[async_trait]
impl Pinner for Latest {
/// Returns the file from the live file system if it exists.
async fn load(&self, name: &str, object_store: &Operator) -> Result<Option<String>> {
async fn load_bytes(&self, name: &str, object_store: &Operator) -> Result<Option<Vec<u8>>> {
let path_str = format!("{}/components/{}", self.store_path, name);

let get_result = object_store.read(&path_str).await?;
let bytes = get_result.to_bytes();
let result = Ok::<Vec<u8>, object_store::Error>(bytes.to_vec());
let res = result.map(|bytes| String::from_utf8(bytes).ok())?;

Ok(res)
Ok(Some(bytes.to_vec()))
}

async fn snapshot(&mut self, _object_store: &Operator) -> Result<String> {
Expand Down
10 changes: 9 additions & 1 deletion src/store/pinner/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,15 @@ pub mod spawn;

#[async_trait]
pub trait Pinner: Debug + Send + Sync {
async fn load(&self, name: &str, fs: &Operator) -> Result<Option<String>>;
async fn load_bytes(&self, name: &str, fs: &Operator) -> Result<Option<Vec<u8>>>;

async fn load(&self, name: &str, fs: &Operator) -> Result<Option<String>> {
match self.load_bytes(name, fs).await? {
Some(bytes) => Ok(Some(String::from_utf8(bytes)?)),
None => Ok(None),
}
}

async fn snapshot(&mut self, fs: &Operator) -> Result<String>;
}

Expand Down
5 changes: 2 additions & 3 deletions src/store/pinner/spawn.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ impl Spawn {
#[async_trait]
impl Pinner for Spawn {
/// Returns the file from the store if it exists.
async fn load(&self, name: &str, object_store: &Operator) -> Result<Option<String>> {
async fn load_bytes(&self, name: &str, object_store: &Operator) -> Result<Option<Vec<u8>>> {
// Borrow files from inside self.files, if not none:
let files = self
.files
Expand All @@ -100,8 +100,7 @@ impl Pinner for Spawn {
match object_store.read(path).await {
Ok(get_result) => {
let bytes = get_result.to_bytes();
let contents = String::from_utf8(bytes.to_vec())?;
Ok(Some(contents))
Ok(Some(bytes.to_vec()))
}
Err(_) => Ok(None),
}
Expand Down
46 changes: 46 additions & 0 deletions src/template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -608,4 +608,50 @@ mod tests {
let result = tmpl.render(context!());
assert!(result.is_err());
}

#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
async fn test_read_file_filter_uses_pinned_store() {
use crate::config::FolderPather;
use crate::store::pinner::snapshot;
use crate::store::pinner::spawn::Spawn;
use opendal::services::Memory;
use opendal::Operator;

let mem_service = Memory::default();
let op = Operator::new(mem_service).unwrap().finish();

// Write a file into the components folder and snapshot it into the pinned store
op.write("components/test.txt", "pinned content")
.await
.unwrap();
let root_hash = snapshot(&op, "pinned/", "components/").await.unwrap();

// Delete the original file so it only exists in the pinned CAS store
op.delete("components/test.txt").await.unwrap();

// Create a Spawn pinner using the snapshot hash
let pinner = Spawn::new_with_root_hash(
"pinned/".to_string(),
"components/".to_string(),
&root_hash,
&op,
)
.await
.unwrap();

let pather = FolderPather {
spawn_folder: "".to_string(),
};
let store = Store::new(Box::new(pinner), op, pather).unwrap();

let mut env = template_env(store, &EngineType::PostgresPSQL).unwrap();
env.add_template(
"test.sql",
r#"{{ "test.txt"|read_file|to_string_lossy|safe }}"#,
)
.unwrap();
let tmpl = env.get_template("test.sql").unwrap();
let result = tmpl.render(context!()).unwrap();
assert_eq!(result, "pinned content");
}
}